From 98287c9019325131b6ccc33e1a4159478e2c6c0c Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 23 Feb 2025 14:05:41 -0700 Subject: [PATCH 001/120] ci(deps): bump `python-semantic-release@v9.20.0` action to 9.21.0 (#1202) --- .github/workflows/cicd.yml | 2 +- .github/workflows/validate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index bcf298dde..2deb632e2 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -139,7 +139,7 @@ jobs: - name: Release | Python Semantic Release id: release - 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 }} root_options: "-v" diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 6f4b50d5f..aab73b6d3 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -102,7 +102,7 @@ jobs: - name: Build | Build next version artifacts id: version - uses: python-semantic-release/python-semantic-release@v9.20.0 + uses: python-semantic-release/python-semantic-release@v9.21.0 with: github_token: "" root_options: "-v" From 84a752b5031c8c06727a1c6d92bcf83081649443 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 23 Feb 2025 14:12:24 -0700 Subject: [PATCH 002/120] ci(deps): bump `mikepenz/action-junit-report@v5.3.0` action to 5.4.0 (#1202) --- .github/workflows/validate.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index aab73b6d3..7ef1e6b85 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -183,7 +183,7 @@ jobs: --junit-xml=tests/reports/pytest-results.xml - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@v5.3.0 + uses: mikepenz/action-junit-report@v5.4.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -271,7 +271,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@v5.3.0 + uses: mikepenz/action-junit-report@v5.4.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -366,7 +366,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@v5.3.0 + uses: mikepenz/action-junit-report@v5.4.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml From 5093f30da0746a9a5302fb37c62d6aae028a930c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Feb 2025 14:32:26 -0700 Subject: [PATCH 003/120] ci(deps): bump `python-semantic-release/publish-action@v9.20.0` to 9.21.0 (#1203) --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 2deb632e2..0dda5e88c 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -146,7 +146,7 @@ jobs: build: false - name: Release | Add distribution artifacts 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 }} From e81a78ae6e21ff8d1adfad470510c7ade03cf496 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 22 Mar 2025 21:31:09 -0600 Subject: [PATCH 004/120] ci(workflows): set 3rd party actions to specific commit hash (#1219) --- .github/workflows/ci.yml | 6 +++--- .github/workflows/cicd.yml | 4 ++-- .github/workflows/manual.yml | 2 +- .github/workflows/validate.yml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36b693d01..92631bc35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: with: fetch-depth: 0 - - uses: wagoid/commitlint-github-action@v6 + - uses: wagoid/commitlint-github-action@b948419dd99f3fd78a6548d48f94e3df7f6bf3ed # v6.2.1 eval-changes: @@ -41,13 +41,13 @@ jobs: - name: Evaluate | Check common file types for changes id: core-changed-files - uses: tj-actions/changed-files@v45.0.7 + uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 #v45.0.9 with: files_yaml_from_source_file: .github/changed-files-spec.yml - name: Evaluate | Check specific file types for changes id: ci-changed-files - uses: tj-actions/changed-files@v45.0.7 + uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 #v45.0.9 with: files_yaml: | ci: diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 0dda5e88c..bc314a0ba 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -26,14 +26,14 @@ jobs: - name: Evaluate | Check common file types for changes id: core-changed-files - uses: tj-actions/changed-files@v45.0.7 + uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 #v45.0.9 with: base_sha: ${{ github.event.push.before }} files_yaml_from_source_file: .github/changed-files-spec.yml - name: Evaluate | Check specific file types for changes id: ci-changed-files - uses: tj-actions/changed-files@v45.0.7 + uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 #v45.0.9 with: base_sha: ${{ github.event.push.before }} files_yaml: | diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index 4e54606dd..64263f37e 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -65,7 +65,7 @@ jobs: python-version: ${{ env.COMMON_PYTHON_VERSION }} - name: Setup | Write file - uses: DamianReeves/write-file-action@v1.3 + uses: DamianReeves/write-file-action@6929a9a6d1807689191dcc8bbe62b54d70a32b42 #v1.3 with: path: .github/manual_eval_input.py write-mode: overwrite diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7ef1e6b85..ac4ec3e72 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -183,7 +183,7 @@ jobs: --junit-xml=tests/reports/pytest-results.xml - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@v5.4.0 + uses: mikepenz/action-junit-report@b14027d33d3a745ccc4d6a12f649e83110b5a373 # v5.4.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -271,7 +271,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@v5.4.0 + uses: mikepenz/action-junit-report@b14027d33d3a745ccc4d6a12f649e83110b5a373 # v5.4.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -366,7 +366,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@v5.4.0 + uses: mikepenz/action-junit-report@b14027d33d3a745ccc4d6a12f649e83110b5a373 # v5.4.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml From a0cd1be4e3aa283cbdc544785e5f895c8391dfb8 Mon Sep 17 00:00:00 2001 From: xakepnz Date: Thu, 17 Apr 2025 13:47:07 +1200 Subject: [PATCH 005/120] build(deps): expand `python-gitlab` dependency to include `v5.0.0` (#1228) The primary change from `v4` to `v5` in this dependency was the removal of identified support for `Python 3.8`. Since we still support `Python 3.8`, then we need to maintain the flexibility for 3.8 package managers to install `v4` while allowing more modern projects to use the new features of `v5` along side `python-semantic-release`. Co-authored-by: Neil McDonald --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 64f665d0b..0b3fa17dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "gitpython ~= 3.0", "requests ~= 2.25", "jinja2 ~= 3.1", - "python-gitlab ~= 4.0", + "python-gitlab >= 4.0.0, < 6.0.0", "tomlkit ~= 0.11", "dotty-dict ~= 1.3", "importlib-resources ~= 6.0", From ca6645793937b9ee792d135c91af6ab8c6c245b0 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 16 Apr 2025 19:43:11 -0600 Subject: [PATCH 006/120] ci(deps): bump `tj-actions/changed-files@v45.0.9` to `v46.0.2` --- .github/workflows/ci.yml | 4 ++-- .github/workflows/cicd.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92631bc35..7a2bda688 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,13 +41,13 @@ jobs: - name: Evaluate | Check common file types for changes id: core-changed-files - uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 #v45.0.9 + uses: tj-actions/changed-files@26a38635fc1173cc5820336ce97be6188d0de9f5 #v46.0.2 with: files_yaml_from_source_file: .github/changed-files-spec.yml - name: Evaluate | Check specific file types for changes id: ci-changed-files - uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 #v45.0.9 + uses: tj-actions/changed-files@26a38635fc1173cc5820336ce97be6188d0de9f5 #v46.0.2 with: files_yaml: | ci: diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index bc314a0ba..955aafa9f 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -26,14 +26,14 @@ jobs: - name: Evaluate | Check common file types for changes id: core-changed-files - uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 #v45.0.9 + uses: tj-actions/changed-files@26a38635fc1173cc5820336ce97be6188d0de9f5 #v46.0.2 with: base_sha: ${{ github.event.push.before }} files_yaml_from_source_file: .github/changed-files-spec.yml - name: Evaluate | Check specific file types for changes id: ci-changed-files - uses: tj-actions/changed-files@a284dc1814e3fd07f2e34267fc8f81227ed29fb8 #v45.0.9 + uses: tj-actions/changed-files@26a38635fc1173cc5820336ce97be6188d0de9f5 #v46.0.2 with: base_sha: ${{ github.event.push.before }} files_yaml: | From d274f0c049f14ec9e8786b4fb1e7a9ac9404f45e Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 16 Apr 2025 19:45:14 -0600 Subject: [PATCH 007/120] ci(deps): bump `mikepenz/action-junit-report@v5.4.0` to `v5.5.0` --- .github/workflows/validate.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ac4ec3e72..df23da85a 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -183,7 +183,7 @@ jobs: --junit-xml=tests/reports/pytest-results.xml - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@b14027d33d3a745ccc4d6a12f649e83110b5a373 # v5.4.0 + uses: mikepenz/action-junit-report@97744eca465b8df9e6e33271cb155003f85327f1 # v5.5.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -271,7 +271,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@b14027d33d3a745ccc4d6a12f649e83110b5a373 # v5.4.0 + uses: mikepenz/action-junit-report@97744eca465b8df9e6e33271cb155003f85327f1 # v5.5.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -366,7 +366,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@b14027d33d3a745ccc4d6a12f649e83110b5a373 # v5.4.0 + uses: mikepenz/action-junit-report@97744eca465b8df9e6e33271cb155003f85327f1 # v5.5.0 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml From 691536e98f311d0fc6d29a72c41ce5a65f1f4b6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 19:07:52 -0700 Subject: [PATCH 008/120] build(deps): bump `rich` dependency from `13.0` to `14.0` (#1224) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0b3fa17dc..db54dde31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ dependencies = [ "dotty-dict ~= 1.3", "importlib-resources ~= 6.0", "pydantic ~= 2.0", - "rich ~= 13.0", + "rich ~= 14.0", "shellingham ~= 1.5", "Deprecated ~= 1.2", # Backport of deprecated decorator for python 3.8 ] From 550e85f5ec2695d5aa680014127846d58c680e31 Mon Sep 17 00:00:00 2001 From: Timon Date: Fri, 18 Apr 2025 07:40:38 +0200 Subject: [PATCH 009/120] docs(github-actions): expound on monorepo example to include publishing actions (#1229) Co-authored-by: codejedi365 --- docs/automatic-releases/github-actions.rst | 95 ++++++++++++++++++---- 1 file changed, 80 insertions(+), 15 deletions(-) diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index d67f6c590..e8d0ce636 100644 --- a/docs/automatic-releases/github-actions.rst +++ b/docs/automatic-releases/github-actions.rst @@ -734,10 +734,6 @@ to the GitHub Release Assets as well. git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - - name: Publish | Upload package to PyPI - uses: pypa/gh-action-pypi-publish@v1 - if: steps.release.outputs.released == 'true' - - name: Publish | Upload to GitHub Release Assets uses: python-semantic-release/publish-action@v9.21.0 if: steps.release.outputs.released == 'true' @@ -745,6 +741,10 @@ to the GitHub Release Assets as well. github_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ steps.release.outputs.tag }} + - name: Publish | Upload package to PyPI + uses: pypa/gh-action-pypi-publish@SHA1_HASH # vX.X.X + if: steps.release.outputs.released == 'true' + .. important:: The `concurrency`_ directive is used on the job to prevent race conditions of more than one release job in the case if there are multiple pushes to ``main`` in a short period @@ -816,19 +816,84 @@ For multiple packages, you would need to run the action multiple times, to relea each project. The following example demonstrates how to release two projects in a monorepo. +Remember that for each release of each submodule you will then need to handle publishing +each package separately as well. This is dependent on the result of your build commands. +In the example below, we assume a simple ``build`` module command to build a ``sdist`` +and wheel artifacts into the submodule's ``dist`` directory. + The ``directory`` input directive is also available for the Python Semantic Release Publish Action. .. code:: yaml - - name: Release Project 1 - 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.21.0 - with: - directory: ./project2 - github_token: ${{ secrets.GITHUB_TOKEN }} + jobs: + + release: + + env: + SUBMODULE_1_DIR: project1 + SUBMODULE_2_DIR: project2 + + steps: + + # ------------------------------------------------------------------- # + # Note the use of different IDs to distinguish which submodule was # + # identified to be released. The subsequent actions then reference # + # their specific release ID to determine if a release occurred. # + # ------------------------------------------------------------------- # + + - name: Release submodule 1 + id: release-submod-1 + uses: python-semantic-release/python-semantic-release@v9.21.0 + with: + directory: ${{ env.SUBMODULE_1_DIR }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Release submodule 2 + id: release-submod-2 + uses: python-semantic-release/python-semantic-release@v9.21.0 + with: + directory: ${{ env.SUBMODULE_2_DIR }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + # ------------------------------------------------------------------- # + # For each submodule, you will have to publish the package separately # + # and only attempt to publish if the release for that submodule was # + # deemed a release (and the release was successful). # + # ------------------------------------------------------------------- # + + - name: Publish | Upload package 1 to GitHub Release Assets + uses: python-semantic-release/publish-action@v9.21.0 + if: steps.release-submod-1.outputs.released == 'true' + with: + directory: ${{ env.SUBMODULE_1_DIR }} + github_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ steps.release-submod-1.outputs.tag }} + + - name: Publish | Upload package 2 to GitHub Release Assets + uses: python-semantic-release/publish-action@v9.21.0 + if: steps.release-submod-2.outputs.released == 'true' + with: + directory: ${{ env.SUBMODULE_2_DIR }} + github_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ steps.release-submod-2.outputs.tag }} + + # ------------------------------------------------------------------- # + # Python Semantic Release is not responsible for publishing your # + # python artifacts to PyPI. Use the official PyPA publish action # + # instead. The following steps are an example but is not guaranteed # + # to work as the action is not maintained by the # + # python-semantic-release team. # + # ------------------------------------------------------------------- # + + - name: Publish | Upload package 1 to PyPI + uses: pypa/gh-action-pypi-publish@SHA1_HASH # vX.X.X + if: steps.release-submod-1.outputs.released == 'true' + with: + packages-dir: ${{ format('{}/dist', env.SUBMODULE_1_DIR) }} + + - name: Publish | Upload package 2 to PyPI + uses: pypa/gh-action-pypi-publish@SHA1_HASH # vX.X.X + if: steps.release-submod-2.outputs.released == 'true' + with: + packages-dir: ${{ format('{}/dist', env.SUBMODULE_2_DIR) }} From f7acbf3d7920489bb1ccef0ddd4ebbf726831620 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:35:26 -0700 Subject: [PATCH 010/120] build(deps-test): expand `pytest-cov` dependency to include `v6` updates (#1231) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index db54dde31..46a885779 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ test = [ "pyyaml ~= 6.0", "pytest ~= 8.3", "pytest-clarity ~= 1.0", - "pytest-cov ~= 5.0", + "pytest-cov >= 5.0.0, < 7.0.0", "pytest-env ~= 1.0", "pytest-lazy-fixtures ~= 1.1.1", "pytest-mock ~= 3.0", From 6e3678a948b62b9af7c4639fa7e199bee5fe8a2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:39:25 -0700 Subject: [PATCH 011/120] build(deps-build): expand `setuptools` dependency to include `v76`, `v77`, & `v78` (#1225) This expansion maintains support for python 3.8 while accepting the new features of setuptools with more modern python versions. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 46a885779..59b7613d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ # Ref: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ # and https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html [build-system] -requires = ["setuptools ~= 75.3.0", "wheel ~= 0.42"] +requires = ["setuptools >= 75.3.0, < 79.0.0", "wheel ~= 0.42"] build-backend = "setuptools.build_meta" [project] From 493b90666cf8a0b32865c239acf80c22017ca576 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 4 May 2025 21:09:30 -0600 Subject: [PATCH 012/120] ci(deps): bump `tj-actions/changed-files@v46.0.2` to `v46.0.5` --- .github/workflows/ci.yml | 4 ++-- .github/workflows/cicd.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a2bda688..53d5c83c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,13 +41,13 @@ jobs: - name: Evaluate | Check common file types for changes id: core-changed-files - uses: tj-actions/changed-files@26a38635fc1173cc5820336ce97be6188d0de9f5 #v46.0.2 + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c #v46.0.5 with: files_yaml_from_source_file: .github/changed-files-spec.yml - name: Evaluate | Check specific file types for changes id: ci-changed-files - uses: tj-actions/changed-files@26a38635fc1173cc5820336ce97be6188d0de9f5 #v46.0.2 + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c #v46.0.5 with: files_yaml: | ci: diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 955aafa9f..870e06e25 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -26,14 +26,14 @@ jobs: - name: Evaluate | Check common file types for changes id: core-changed-files - uses: tj-actions/changed-files@26a38635fc1173cc5820336ce97be6188d0de9f5 #v46.0.2 + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c #v46.0.5 with: base_sha: ${{ github.event.push.before }} files_yaml_from_source_file: .github/changed-files-spec.yml - name: Evaluate | Check specific file types for changes id: ci-changed-files - uses: tj-actions/changed-files@26a38635fc1173cc5820336ce97be6188d0de9f5 #v46.0.2 + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c #v46.0.5 with: base_sha: ${{ github.event.push.before }} files_yaml: | From ef7d4ff2e2e9452a21c016bee9632db8b37d35e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 18:49:31 +0000 Subject: [PATCH 013/120] ci(deps): bump `mikepenz/action-junit-report@5.5.0` to `v5.5.1` --- .github/workflows/validate.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index df23da85a..91848725a 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -183,7 +183,7 @@ jobs: --junit-xml=tests/reports/pytest-results.xml - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@97744eca465b8df9e6e33271cb155003f85327f1 # v5.5.0 + uses: mikepenz/action-junit-report@cf701569b05ccdd861a76b8607a66d76f6fd4857 # v5.5.1 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -271,7 +271,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@97744eca465b8df9e6e33271cb155003f85327f1 # v5.5.0 + uses: mikepenz/action-junit-report@cf701569b05ccdd861a76b8607a66d76f6fd4857 # v5.5.1 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml @@ -366,7 +366,7 @@ jobs: retention-days: 1 - name: Report | Upload Test Results - uses: mikepenz/action-junit-report@97744eca465b8df9e6e33271cb155003f85327f1 # v5.5.0 + uses: mikepenz/action-junit-report@cf701569b05ccdd861a76b8607a66d76f6fd4857 # v5.5.1 if: ${{ always() && steps.tests.outcome != 'skipped' }} with: report_paths: ./tests/reports/*.xml From f7ef0e41308342ddbf68112ed6133b95707bcfa1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 May 2025 20:25:10 -0700 Subject: [PATCH 014/120] build(deps-build): expand setuptools dependency to include `v79` & `v80` (#1237) Co-authored-by: codejedi365 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 59b7613d6..49e05922e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ # Ref: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ # and https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html [build-system] -requires = ["setuptools >= 75.3.0, < 79.0.0", "wheel ~= 0.42"] +requires = ["setuptools >= 75.3.0, < 81.0.0", "wheel ~= 0.42"] build-backend = "setuptools.build_meta" [project] From 71ab2cff88ae9f0ad9dbdcb77bff448aee36cfb0 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 4 May 2025 22:05:27 -0600 Subject: [PATCH 015/120] refactor: pull project version variable from package metadata (#1240) * chore(project-config): remove the source code version stamp during release --- pyproject.toml | 1 - src/semantic_release/__init__.py | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 49e05922e..7379b0af3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -412,7 +412,6 @@ build_command = """ python -m build . """ major_on_zero = true -version_variables = ["src/semantic_release/__init__.py:__version__"] version_toml = ["pyproject.toml:project.version"] [tool.semantic_release.changelog] diff --git a/src/semantic_release/__init__.py b/src/semantic_release/__init__.py index fa54ef662..25deef686 100644 --- a/src/semantic_release/__init__.py +++ b/src/semantic_release/__init__.py @@ -2,6 +2,8 @@ from __future__ import annotations +import importlib.metadata + from semantic_release.commit_parser import ( CommitParser, ParsedCommit, @@ -24,7 +26,7 @@ tags_and_versions, ) -__version__ = "9.21.0" +__version__ = importlib.metadata.version(f"python_{__package__}".replace("_", "-")) __all__ = [ "CommitParser", From f61f8a38a1a3f44a7a56cf9dcb7dde748f90ca1e Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 4 May 2025 22:20:02 -0600 Subject: [PATCH 016/120] fix(changelog-filters): fixes url resolution when prefix & path share letters (#1239) The change adjusts the url normalization computation to remove any path prefix and only the path prefix even when letters are shared with the rest of the url path. Resolves: #1204 * test(hvcs-bitbucket): add validation for self hosted server commit url --- src/semantic_release/hvcs/remote_hvcs_base.py | 18 +++++++++++---- .../semantic_release/hvcs/test_bitbucket.py | 23 +++++++++++++++++++ .../unit/semantic_release/hvcs/test_gitea.py | 23 +++++++++++++++++++ .../unit/semantic_release/hvcs/test_github.py | 23 +++++++++++++++++++ .../unit/semantic_release/hvcs/test_gitlab.py | 23 +++++++++++++++++++ 5 files changed, 106 insertions(+), 4 deletions(-) diff --git a/src/semantic_release/hvcs/remote_hvcs_base.py b/src/semantic_release/hvcs/remote_hvcs_base.py index e7cd93ab3..d26b01881 100644 --- a/src/semantic_release/hvcs/remote_hvcs_base.py +++ b/src/semantic_release/hvcs/remote_hvcs_base.py @@ -95,10 +95,15 @@ def create_server_url( query: str | None = None, fragment: str | None = None, ) -> str: - # Ensure any path prefix is transfered but not doubled up on the derived url + # Ensure any path prefix is transferred but not doubled up on the derived url + normalized_path = ( + f"{self.hvcs_domain.path}/{path}" + if self.hvcs_domain.path and not path.startswith(self.hvcs_domain.path) + else path + ) return self._derive_url( self.hvcs_domain, - path=f"{self.hvcs_domain.path or ''}/{path.lstrip(self.hvcs_domain.path)}", + path=normalized_path, auth=auth, query=query, fragment=fragment, @@ -123,10 +128,15 @@ def create_api_url( query: str | None = None, fragment: str | None = None, ) -> str: - # Ensure any api path prefix is transfered but not doubled up on the derived api url + # Ensure any api path prefix is transferred but not doubled up on the derived api url + normalized_endpoint = ( + f"{self.api_url.path}/{endpoint}" + if self.api_url.path and not endpoint.startswith(self.api_url.path) + else endpoint + ) return self._derive_url( self.api_url, - path=f"{self.api_url.path or ''}/{endpoint.lstrip(self.api_url.path)}", + path=normalized_endpoint, auth=auth, query=query, fragment=fragment, diff --git a/tests/unit/semantic_release/hvcs/test_bitbucket.py b/tests/unit/semantic_release/hvcs/test_bitbucket.py index 85f1d7e46..16d77fb87 100644 --- a/tests/unit/semantic_release/hvcs/test_bitbucket.py +++ b/tests/unit/semantic_release/hvcs/test_bitbucket.py @@ -301,6 +301,29 @@ def test_commit_hash_url(default_bitbucket_client: Bitbucket): assert expected_url == default_bitbucket_client.commit_hash_url(sha) +def test_commit_hash_url_w_custom_server(): + """ + Test the commit hash URL generation for a self-hosted Bitbucket server with prefix. + + ref: https://github.com/python-semantic-release/python-semantic-release/issues/1204 + """ + sha = "244f7e11bcb1e1ce097db61594056bc2a32189a0" + expected_url = "{server}/{owner}/{repo}/commits/{sha}".format( + server=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo", + owner="foo", + repo=EXAMPLE_REPO_NAME, + sha=sha, + ) + + with mock.patch.dict(os.environ, {}, clear=True): + actual_url = Bitbucket( + remote_url=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo/foo/{EXAMPLE_REPO_NAME}.git", + hvcs_domain=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo", + ).commit_hash_url(sha) + + assert expected_url == actual_url + + @pytest.mark.parametrize("pr_number", (666, "666", "#666")) def test_pull_request_url(default_bitbucket_client: Bitbucket, pr_number: int | str): expected_url = "{server}/{owner}/{repo}/pull-requests/{pr_number}".format( diff --git a/tests/unit/semantic_release/hvcs/test_gitea.py b/tests/unit/semantic_release/hvcs/test_gitea.py index 710b01b08..d98be8e96 100644 --- a/tests/unit/semantic_release/hvcs/test_gitea.py +++ b/tests/unit/semantic_release/hvcs/test_gitea.py @@ -203,6 +203,29 @@ def test_commit_hash_url(default_gitea_client: Gitea): assert expected_url == default_gitea_client.commit_hash_url(sha) +def test_commit_hash_url_w_custom_server(): + """ + Test the commit hash URL generation for a self-hosted Bitbucket server with prefix. + + ref: https://github.com/python-semantic-release/python-semantic-release/issues/1204 + """ + sha = "244f7e11bcb1e1ce097db61594056bc2a32189a0" + expected_url = "{server}/{owner}/{repo}/commit/{sha}".format( + server=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo", + owner="foo", + repo=EXAMPLE_REPO_NAME, + sha=sha, + ) + + with mock.patch.dict(os.environ, {}, clear=True): + actual_url = Gitea( + remote_url=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo/foo/{EXAMPLE_REPO_NAME}.git", + hvcs_domain=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo", + ).commit_hash_url(sha) + + assert expected_url == actual_url + + @pytest.mark.parametrize("issue_number", (666, "666", "#666")) def test_issue_url(default_gitea_client: Gitea, issue_number: int | str): expected_url = "{server}/{owner}/{repo}/issues/{issue_number}".format( diff --git a/tests/unit/semantic_release/hvcs/test_github.py b/tests/unit/semantic_release/hvcs/test_github.py index f52482ebf..e7f69a5ea 100644 --- a/tests/unit/semantic_release/hvcs/test_github.py +++ b/tests/unit/semantic_release/hvcs/test_github.py @@ -375,6 +375,29 @@ def test_commit_hash_url(default_gh_client: Github): assert expected_url == default_gh_client.commit_hash_url(sha) +def test_commit_hash_url_w_custom_server(): + """ + Test the commit hash URL generation for a self-hosted Bitbucket server with prefix. + + ref: https://github.com/python-semantic-release/python-semantic-release/issues/1204 + """ + sha = "244f7e11bcb1e1ce097db61594056bc2a32189a0" + expected_url = "{server}/{owner}/{repo}/commit/{sha}".format( + server=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo", + owner="foo", + repo=EXAMPLE_REPO_NAME, + sha=sha, + ) + + with mock.patch.dict(os.environ, {}, clear=True): + actual_url = Github( + remote_url=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo/foo/{EXAMPLE_REPO_NAME}.git", + hvcs_domain=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo", + ).commit_hash_url(sha) + + assert expected_url == actual_url + + @pytest.mark.parametrize("issue_number", (666, "666", "#666")) def test_issue_url(default_gh_client: Github, issue_number: str | int): expected_url = "{server}/{owner}/{repo}/issues/{issue_num}".format( diff --git a/tests/unit/semantic_release/hvcs/test_gitlab.py b/tests/unit/semantic_release/hvcs/test_gitlab.py index c4a0979fe..3011de8bb 100644 --- a/tests/unit/semantic_release/hvcs/test_gitlab.py +++ b/tests/unit/semantic_release/hvcs/test_gitlab.py @@ -260,6 +260,29 @@ def test_commit_hash_url(default_gl_client: Gitlab): assert expected_url == default_gl_client.commit_hash_url(REF) +def test_commit_hash_url_w_custom_server(): + """ + Test the commit hash URL generation for a self-hosted Bitbucket server with prefix. + + ref: https://github.com/python-semantic-release/python-semantic-release/issues/1204 + """ + sha = "244f7e11bcb1e1ce097db61594056bc2a32189a0" + expected_url = "{server}/{owner}/{repo}/-/commit/{sha}".format( + server=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo", + owner="foo", + repo=EXAMPLE_REPO_NAME, + sha=sha, + ) + + with mock.patch.dict(os.environ, {}, clear=True): + actual_url = Gitlab( + remote_url=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo/foo/{EXAMPLE_REPO_NAME}.git", + hvcs_domain=f"https://{EXAMPLE_HVCS_DOMAIN}/projects/demo-foo", + ).commit_hash_url(sha) + + assert expected_url == actual_url + + @pytest.mark.parametrize("issue_number", (666, "666", "#666")) def test_issue_url(default_gl_client: Gitlab, issue_number: int | str): expected_url = "{server}/{owner}/{repo}/-/issues/{issue_num}".format( From 0dc72ac9058a62054a45f6344c83a423d7f906a8 Mon Sep 17 00:00:00 2001 From: semantic-release Date: Mon, 5 May 2025 04:28:43 +0000 Subject: [PATCH 017/120] 9.21.1 Automatically generated by python-semantic-release --- CHANGELOG.rst | 35 ++++++++++++++++++++++ docs/automatic-releases/github-actions.rst | 18 +++++------ pyproject.toml | 2 +- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0e07247dc..e802155f2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,41 @@ CHANGELOG ========= +.. _changelog-v9.21.1: + +v9.21.1 (2025-05-05) +==================== + +πŸͺ² Bug Fixes +------------ + +* **changelog-filters**: Fixes url resolution when prefix & path share letters, closes `#1204`_ + (`PR#1239`_, `f61f8a3`_) + +πŸ“– Documentation +---------------- + +* **github-actions**: Expound on monorepo example to include publishing actions (`PR#1229`_, + `550e85f`_) + +βš™οΈ Build System +---------------- + +* **deps**: Bump ``rich`` dependency from ``13.0`` to ``14.0`` (`PR#1224`_, `691536e`_) + +* **deps**: Expand ``python-gitlab`` dependency to include ``v5.0.0`` (`PR#1228`_, `a0cd1be`_) + +.. _#1204: https://github.com/python-semantic-release/python-semantic-release/issues/1204 +.. _550e85f: https://github.com/python-semantic-release/python-semantic-release/commit/550e85f5ec2695d5aa680014127846d58c680e31 +.. _691536e: https://github.com/python-semantic-release/python-semantic-release/commit/691536e98f311d0fc6d29a72c41ce5a65f1f4b6c +.. _a0cd1be: https://github.com/python-semantic-release/python-semantic-release/commit/a0cd1be4e3aa283cbdc544785e5f895c8391dfb8 +.. _f61f8a3: https://github.com/python-semantic-release/python-semantic-release/commit/f61f8a38a1a3f44a7a56cf9dcb7dde748f90ca1e +.. _PR#1224: https://github.com/python-semantic-release/python-semantic-release/pull/1224 +.. _PR#1228: https://github.com/python-semantic-release/python-semantic-release/pull/1228 +.. _PR#1229: https://github.com/python-semantic-release/python-semantic-release/pull/1229 +.. _PR#1239: https://github.com/python-semantic-release/python-semantic-release/pull/1239 + + .. _changelog-v9.21.0: v9.21.0 (2025-02-23) diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index e8d0ce636..219e0273c 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.21.0 + - uses: python-semantic-release/python-semantic-release@v9.21.1 with: root_options: "-vv --noop" @@ -576,7 +576,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v9.21.0 + - uses: python-semantic-release/publish-action@v9.21.1 with: root_options: "-vv --noop" @@ -728,14 +728,14 @@ to the GitHub Release Assets as well. - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v9.21.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.0 + uses: python-semantic-release/publish-action@v9.21.1 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -794,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.21.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -844,14 +844,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v9.21.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: directory: ${{ env.SUBMODULE_1_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release submodule 2 id: release-submod-2 - uses: python-semantic-release/python-semantic-release@v9.21.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -863,7 +863,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.0 + uses: python-semantic-release/publish-action@v9.21.1 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -871,7 +871,7 @@ Publish Action. tag: ${{ steps.release-submod-1.outputs.tag }} - name: Publish | Upload package 2 to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.0 + uses: python-semantic-release/publish-action@v9.21.1 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/pyproject.toml b/pyproject.toml index 7379b0af3..074ab8c4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "9.21.0" +version = "9.21.1" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } From e39ceeb7955f2439a5e6ffe45a10feb92e09e295 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 4 May 2025 23:19:53 -0600 Subject: [PATCH 018/120] ci(deps): bump `python-semantic-release@v9.21.0` action to `v9.21.1` --- .github/workflows/cicd.yml | 2 +- .github/workflows/validate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 870e06e25..cc5c2c4f2 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -139,7 +139,7 @@ jobs: - name: Release | Python Semantic Release id: release - uses: python-semantic-release/python-semantic-release@v9.21.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} root_options: "-v" diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 91848725a..08cbb4c91 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -102,7 +102,7 @@ jobs: - name: Build | Build next version artifacts id: version - uses: python-semantic-release/python-semantic-release@v9.21.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: github_token: "" root_options: "-v" From 31abbc7e4555949a93ac1bd428c8f998277b37c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 05:15:17 +0000 Subject: [PATCH 019/120] ci(deps): bump `python-semantic-release/publish-action@v9.21.0` action to `v9.21.1` --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index cc5c2c4f2..13dc13bda 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -146,7 +146,7 @@ jobs: build: false - name: Release | Add distribution artifacts to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.0 + uses: python-semantic-release/publish-action@v9.21.1 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} From 4aa6a6edbff75889e09f32f7cba52cb90c9fb626 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Thu, 15 May 2025 22:50:49 -0600 Subject: [PATCH 020/120] build(deps): prevent update to `click@8.2.0` (#1245) click 8.2 has a breaking change in the test suite where it removes a parameter from the cli runner. There is additional deprecation warnings included as well so for now since it will break our tests limit this to 8.1.0 only. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 074ab8c4b..ca6945374 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ readme = "README.rst" authors = [{ name = "Rolf Erik Lekang", email = "me@rolflekang.com" }] dependencies = [ - "click ~= 8.0", + "click ~= 8.1.0", "click-option-group ~= 0.5", "gitpython ~= 3.0", "requests ~= 2.25", From 080e4bcb14048a2dd10445546a7ee3159b3ab85c Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Thu, 15 May 2025 23:24:02 -0600 Subject: [PATCH 021/120] feat(cmd-version): enable `version_variables` version stamp of vars with double-equals (#1244) * test(stamp-version): add test case for stamping a `requirements.txt` file with double-equals * docs(configuration): update `version_variables` section to include double-equals operand support --- docs/configuration.rst | 6 +++++- src/semantic_release/version/declarations/pattern.py | 4 ++-- .../version/declarations/test_pattern_declaration.py | 9 +++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 78e20d183..aa904dc9d 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1347,7 +1347,8 @@ The regular expression generated from the ``version_variables`` definition will: 2. The variable name defined by ``variable`` and the version must be separated by an operand symbol (``=``, ``:``, ``:=``, or ``@``). Whitespace is optional around - the symbol. + the symbol. As of $NEW_VERSION, a double-equals (``==``) operator is also supported + as a valid operand symbol. 3. The value of the variable must match a `SemVer`_ regular expression and can be enclosed by single (``'``) or double (``"``) quotation marks but they must match. However, @@ -1400,6 +1401,9 @@ will be matched and replaced by the new version: # Custom Tag Format with tag_format set (monorepos) __release__ = "module-v1.2.3" + # requirements.txt + my-package == 1.2.3 + .. important:: The Regular Expression expects a version value to exist in the file to be replaced. It cannot be an empty string or a non-semver compliant string. If this is the very diff --git a/src/semantic_release/version/declarations/pattern.py b/src/semantic_release/version/declarations/pattern.py index 55873ce0a..e96234117 100644 --- a/src/semantic_release/version/declarations/pattern.py +++ b/src/semantic_release/version/declarations/pattern.py @@ -230,9 +230,9 @@ def from_string_definition( # Supports optional matching quotations around variable name # Negative lookbehind to ensure we don't match part of a variable name f"""(?x)(?P['"])?(?['"])?{value_replace_pattern_str}(?P=quote2)?""", ], diff --git a/tests/unit/semantic_release/version/declarations/test_pattern_declaration.py b/tests/unit/semantic_release/version/declarations/test_pattern_declaration.py index fd7cb7dad..8280e95bb 100644 --- a/tests/unit/semantic_release/version/declarations/test_pattern_declaration.py +++ b/tests/unit/semantic_release/version/declarations/test_pattern_declaration.py @@ -165,6 +165,15 @@ def test_pattern_declaration_is_version_replacer(): """if version := '1.0.0': """, f"""if version := '{next_version}': """, ), + ( + "Explicit number format for requirements.txt file with double equals", + f"{test_file}:my-package:{VersionStampType.NUMBER_FORMAT.value}", + # irrelevant for this case + lazy_fixture(default_tag_format_str.__name__), + # Uses double equals separator + """my-package == 1.0.0""", + f"""my-package == {next_version}""", + ), ( "Using default number format for multi-line & quoted json", f"{test_file}:version:{VersionStampType.NUMBER_FORMAT.value}", From fb3da27650ff15bcdb3b7badc919bd8a9a73238d Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 12 May 2025 22:50:05 -0600 Subject: [PATCH 022/120] fix(github-action)!: resolve command injection vulnerability in action script Prevents the malicious execution of arbitrary code when a command injection is defined in the action yaml as part of the action parameter specification. Low impact exploitation if proper least privilege is implemented and pull request code is reviewed before merging to a release branch. BREAKING CHANGE: The `root_options` action input parameter has been removed because it created a command injection vulernability for arbitrary code to execute within the container context of the GitHub action if a command injection code was provided as part of the `root_options` parameter string. To eliminate the vulnerability, each relevant option that can be provided to `semantic-release` has been individually added as its own parameter and will be processed individually to prevent command injection. Please review our `Github Actions Configuration`__ page on the Python Semantic Release Documentation website to review the newly available configuration options that replace the `root_options` parameter. __ https://python-semantic-release.readthedocs.io/en/stable/automatic-releases/github-actions.html --- action.sh | 61 +++++++++++++++++++++++++++++++++++++++++++++++++----- action.yml | 49 +++++++++++++++++++++++++++---------------- 2 files changed, 87 insertions(+), 23 deletions(-) diff --git a/action.sh b/action.sh index 48ab72c35..e70e39d4a 100644 --- a/action.sh +++ b/action.sh @@ -2,6 +2,12 @@ set -e +explicit_run_cmd() { + local cmd="$*" + printf '%s\n' "$> $cmd" + eval "$cmd" +} + # Convert "true"/"false" into command line args, returns "" if not defined eval_boolean_action_input() { local -r input_name="$1" @@ -13,11 +19,11 @@ eval_boolean_action_input() { local -r if_false="$1" if [ -z "$flag_value" ]; then - echo "" + printf "" elif [ "$flag_value" = "true" ]; then - echo "$if_true" + printf '%s\n' "$if_true" elif [ "$flag_value" = "false" ]; then - echo "$if_false" + printf '%s\n' "$if_false" else printf 'Error: Invalid value for input %s: %s is not "true" or "false\n"' \ "$input_name" "$flag_value" >&2 @@ -25,8 +31,53 @@ eval_boolean_action_input() { fi } +# Convert string input into command line args, returns "" if undefined +eval_string_input() { + local -r input_name="$1" + shift + local -r if_defined="$1" + shift + local value + value="$(printf '%s' "$1" | tr -d ' ')" + + if [ -z "$value" ]; then + printf "" + return 0 + fi + + printf '%s' "${if_defined/\%s/$value}" +} + # Convert inputs to command line arguments -export ARGS=() +ROOT_OPTIONS=() + +if ! printf '%s\n' "$INPUT_VERBOSITY" | grep -qE '^[0-9]+$'; then + printf "Error: Input 'verbosity' must be a positive integer\n" >&2 + exit 1 +fi + +VERBOSITY_OPTIONS="" +for ((i = 0; i < INPUT_VERBOSITY; i++)); do + [ "$i" -eq 0 ] && VERBOSITY_OPTIONS="-" + VERBOSITY_OPTIONS+="v" +done + +ROOT_OPTIONS+=("$VERBOSITY_OPTIONS") + +if [ -n "$INPUT_CONFIG_FILE" ]; then + # Check if the file exists + if [ ! -f "$INPUT_CONFIG_FILE" ]; then + printf "Error: Input 'config_file' does not exist: %s\n" "$INPUT_CONFIG_FILE" >&2 + exit 1 + fi + + ROOT_OPTIONS+=("$(eval_string_input "config_file" "--config %s" "$INPUT_CONFIG_FILE")") || exit 1 +fi + +ROOT_OPTIONS+=("$(eval_boolean_action_input "strict" "$INPUT_STRICT" "--strict" "")") || exit 1 +ROOT_OPTIONS+=("$(eval_boolean_action_input "no_operation_mode" "$INPUT_NO_OPERATION_MODE" "--noop" "")") || exit 1 + +ARGS=() # v10 Breaking change as prerelease should be as_prerelease to match ARGS+=("$(eval_boolean_action_input "prerelease" "$INPUT_PRERELEASE" "--as-prerelease" "")") || exit 1 ARGS+=("$(eval_boolean_action_input "commit" "$INPUT_COMMIT" "--commit" "--no-commit")") || exit 1 @@ -111,4 +162,4 @@ fi export GH_TOKEN="${INPUT_GITHUB_TOKEN}" # Run Semantic Release (explicitly use the GitHub action version) -eval "/psr/.venv/bin/semantic-release $INPUT_ROOT_OPTIONS version ${ARGS[*]}" +explicit_run_cmd "/psr/.venv/bin/semantic-release ${ROOT_OPTIONS[*]} version ${ARGS[*]}" diff --git a/action.yml b/action.yml index 992b5d05d..7b9e3c654 100644 --- a/action.yml +++ b/action.yml @@ -7,11 +7,14 @@ branding: color: orange inputs: - root_options: - default: "-v" + + config_file: + default: "" required: false description: | - Additional options for the main command. Example: -vv --noop + Path to a custom semantic-release configuration file. By default, an empty + string will look for a pyproject.toml file in the current directory. This is the same + as passing the `-c` or `--config` parameter to semantic-release. directory: default: "." @@ -19,64 +22,78 @@ inputs: description: Sub-directory to cd into before running semantic-release github_token: - type: string required: true description: GitHub token used to push release notes and new commits/tags git_committer_name: - type: string required: false description: The human name for the β€œcommitter” field git_committer_email: - type: string required: false description: The email address for the β€œcommitter” field + no_operation_mode: + default: "false" + required: false + description: | + If set to true, the github action will pass the `--noop` parameter to + semantic-release. This will cause semantic-release to run in "no operation" + mode. See the documentation for more information on this parameter. Note that, + this parameter will not affect the output of the action, so you will still get + the version determination decision as output values. + ssh_public_signing_key: - type: string required: false description: The ssh public key used to sign commits ssh_private_signing_key: - type: string required: false description: The ssh private key used to sign commits + strict: + default: "false" + required: false + description: | + If set to true, the github action will pass the `--strict` parameter to + semantic-release. See the documentation for more information on this parameter. + + verbosity: + default: "1" + required: false + description: | + Set the verbosity level of the output as the number of -v's to pass to + semantic-release. 0 is no extra output, 1 is info level output, 2 is + debug output, and 3 is silly debug level of output. + # `semantic-release version` command line options prerelease: - type: string required: false description: | Force the next version to be a prerelease. Set to "true" or "false". prerelease_token: - type: string required: false description: "Force the next version to use this prerelease token, if it is a prerelease" force: - type: string required: false description: | Force the next version to be a major release. Must be set to one of "prerelease", "patch", "minor", or "major". commit: - type: string required: false description: Whether or not to commit changes locally. Defaults are handled by python-semantic-release internal version command. tag: - type: string required: false description: | Whether or not to make a local version tag. Defaults are handled by python-semantic-release internal version command. push: - type: string required: false description: | Whether or not to push local commits to the Git repository. See @@ -84,26 +101,22 @@ inputs: for how the default is determined between push, tag, & commit. changelog: - type: string required: false description: | Whether or not to update the changelog. vcs_release: - type: string required: false description: | Whether or not to create a release in the remote VCS, if supported build: - type: string required: false description: | Whether or not to run the build_command for the project. Defaults are handled by python-semantic-release internal version command. build_metadata: - type: string required: false description: | Build metadata to append to the new version From a08289693085153effdafe3c6ff235a1777bb1fa Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Tue, 13 May 2025 00:38:41 -0600 Subject: [PATCH 023/120] docs(github-actions): update PSR action parameter documenation --- docs/automatic-releases/github-actions.rst | 78 ++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index 219e0273c..4b1b7ca17 100644 --- a/docs/automatic-releases/github-actions.rst +++ b/docs/automatic-releases/github-actions.rst @@ -163,6 +163,25 @@ to the index during the build command. This option is equivalent to adding eithe ---- +.. _gh_actions-psr-inputs-config_file: + +``config_file`` +""""""""""""""" + +Path to a custom semantic-release configuration file. By default, an empty +string will look for to the ``pyproject.toml`` file in the current directory. +This is the same as passing the ``-c`` or ``--config`` parameter to semantic-release. + +**Required:** ``false`` + +**Default:** ``""`` + +.. seealso:: + + - :ref:`cmd-main-option-config` for the :ref:`semantic-release ` command + +---- + .. _gh_actions-psr-inputs-directory: ``directory`` @@ -248,6 +267,27 @@ The token should have the following `permissions`_: ---- +.. _gh_actions-psr-inputs-noop: + +``no_operation_mode`` +""""""""""""""""""""" + +If set to true, the github action will pass the ``--noop`` parameter to +semantic-release. This will cause semantic-release to run in "no operation" +mode. + +This is useful for testing the action without making any permanent changes to the repository. + +**Required:** ``false`` + +**Default:** ``false`` + +.. seealso:: + + - :ref:`cmd-main-option-noop` option for the :ref:`semantic-release ` command + +---- + .. _gh_actions-psr-inputs-prerelease: ``prerelease`` @@ -330,6 +370,11 @@ to the remote repository. This option is equivalent to adding either ``--push`` ``root_options`` """""""""""""""" +.. important:: + This option has been removed in version $NEW_VERSION and newer because of a + command injection vulnerability. Please update as to $NEW_RELEASE_TAG as soon + as possible. + Additional options for the main ``semantic-release`` command, which will come before the :ref:`version ` subcommand. @@ -377,6 +422,20 @@ The private key used to sign a commit and tag. ---- +.. _gh_actions-psr-inputs-strict: + +``strict`` +"""""""""" + +If set to true, the github action will pass the `--strict` parameter to +``semantic-release``. + +.. seealso:: + + - :ref:`cmd-main-option-strict` option for the :ref:`semantic-release ` command + +---- + .. _gh_actions-psr-inputs-tag: ``tag`` @@ -424,6 +483,25 @@ equivalent to adding either ``--vcs-release`` (on ``true``) or ``--no-vcs-releas ---- +.. _gh_actions-psr-inputs-verbosity: + +``verbosity`` +""""""""""""" + +Set the verbosity level of the output as the number of ``-v``'s to pass to +``semantic-release``. 0 is no extra output, 1 is info level output, 2 is debug output, and +3 is a silly amount of debug output. + +**Required:** ``false`` + +**Default:** ``"1"`` + +.. seealso:: + + - :ref:`cmd-main-option-verbosity` for the :ref:`semantic-release ` command + +---- + .. _gh_actions-psr-outputs: Outputs From c4d45ec46dfa81f645c25ea18ffffe9635922603 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Tue, 13 May 2025 00:40:08 -0600 Subject: [PATCH 024/120] docs(github-actions): update `python-semantic-release/publish-action` parameter notes --- docs/automatic-releases/github-actions.rst | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index 4b1b7ca17..19476e861 100644 --- a/docs/automatic-releases/github-actions.rst +++ b/docs/automatic-releases/github-actions.rst @@ -609,6 +609,25 @@ supported input and its purpose. ---- +.. _gh_actions-publish-inputs-config_file: + +``config_file`` +""""""""""""""" + +Path to a custom semantic-release configuration file. By default, an empty +string will look for to the ``pyproject.toml`` file in the current directory. +This is the same as passing the ``-c`` or ``--config`` parameter to semantic-release. + +**Required:** ``false`` + +**Default:** ``""`` + +.. seealso:: + + - :ref:`cmd-main-option-config` for the :ref:`semantic-release ` command + +---- + .. _gh_actions-publish-inputs-directory: ``directory`` @@ -642,11 +661,37 @@ The token should have the following `permissions`_: ---- +.. _gh_actions-publish-inputs-noop: + +``no_operation_mode`` +""""""""""""""""""""" + +If set to true, the github action will pass the ``--noop`` parameter to +semantic-release. This will cause semantic-release to run in "no operation" +mode. + +This is useful for testing the action without actually publishing anything. + +**Required:** ``false`` + +**Default:** ``false`` + +.. seealso:: + + - :ref:`cmd-main-option-noop` option for the :ref:`semantic-release ` command + +---- + .. _gh_actions-publish-inputs-root_options: ``root_options`` """""""""""""""" +.. important:: + This option has been removed in version $NEW_VERSION and newer because of a + command injection vulnerability. Please update as to $NEW_RELEASE_TAG as soon + as possible. + Additional options for the main ``semantic-release`` command, which will come before the :ref:`publish ` subcommand. @@ -698,6 +743,25 @@ Python Semantic Release will automatically determine the latest release if no ---- +.. _gh_actions-publish-inputs-verbosity: + +``verbosity`` +""""""""""""" + +Set the verbosity level of the output as the number of ``-v``'s to pass to +``semantic-release``. 0 is no extra output, 1 is info level output, 2 is debug output, and +3 is a silly amount of debug output. + +**Required:** ``false`` + +**Default:** ``"1"`` + +.. seealso:: + + - :ref:`cmd-main-option-verbosity` for the :ref:`semantic-release ` command + +---- + .. _gh_actions-publish-outputs: Outputs From 67b2ae0050cce540a4126fe280cca6dc4bcf5d3f Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Tue, 13 May 2025 21:22:56 -0600 Subject: [PATCH 025/120] docs(github-actions): change recommended workflow to separate release from deploy --- docs/automatic-releases/github-actions.rst | 51 ++++++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index 19476e861..621a13067 100644 --- a/docs/automatic-releases/github-actions.rst +++ b/docs/automatic-releases/github-actions.rst @@ -800,6 +800,10 @@ to the GitHub Release Assets as well. branches: - main + # default: least privileged permissions across all jobs + permissions: + contents: read + jobs: release: runs-on: ubuntu-latest @@ -808,7 +812,6 @@ to the GitHub Release Assets as well. cancel-in-progress: false permissions: - id-token: write contents: write steps: @@ -883,9 +886,49 @@ to the GitHub Release Assets as well. github_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ steps.release.outputs.tag }} - - name: Publish | Upload package to PyPI - uses: pypa/gh-action-pypi-publish@SHA1_HASH # vX.X.X - if: steps.release.outputs.released == 'true' + - name: Upload | Distribution Artifacts + uses: actions/upload-artifact@v4 + with: + name: distribution-artifacts + path: dist + if-no-files-found: error + + deploy: + # 1. Separate out the deploy step from the publish step to run each step at + # the least amount of token privilege + # 2. Also, deployments can fail, and its better to have a separate job if you need to retry + # and it won't require reversing the release. + runs-on: ubuntu-latest + needs: release + if: ${{ needs.release.outputs.released == 'true' }} + + permissions: + contents: read + id-token: write + + steps: + - name: Setup | Download Build Artifacts + uses: actions/download-artifact@v4 + id: artifact-download + with: + name: distribution-artifacts + path: dist + + # ------------------------------------------------------------------- # + # Python Semantic Release is not responsible for publishing your # + # python artifacts to PyPI. Use the official PyPA publish action # + # instead. The following steps are an example but is not guaranteed # + # to work as the action is not maintained by the # + # python-semantic-release team. # + # ------------------------------------------------------------------- # + + # see https://docs.pypi.org/trusted-publishers/ + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@@SHA1_HASH # vX.X.X + with: + packages-dir: dist + print-hash: true + verbose: true .. important:: The `concurrency`_ directive is used on the job to prevent race conditions of more than From 47d1b74780a04bfb0bb0f31f0bdc19a094bc77e7 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Tue, 13 May 2025 22:08:03 -0600 Subject: [PATCH 026/120] refactor(github-actions): consolidate code & simplify github action build --- Dockerfile | 33 ---------------------- MANIFEST.in | 8 +++++- action.yml | 2 +- src/gh_action/.dockerignore | 6 ++++ src/gh_action/Dockerfile | 42 ++++++++++++++++++++++++++++ action.sh => src/gh_action/action.sh | 8 ++++-- src/gh_action/requirements.txt | 1 + 7 files changed, 63 insertions(+), 37 deletions(-) delete mode 100644 Dockerfile create mode 100644 src/gh_action/.dockerignore create mode 100644 src/gh_action/Dockerfile rename action.sh => src/gh_action/action.sh (94%) create mode 100644 src/gh_action/requirements.txt diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 489b41bee..000000000 --- a/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# This Dockerfile is only for GitHub Actions -FROM python:3.13-bookworm - -# Copy python-semantic-release source code into container -COPY . /psr - -RUN \ - # Install desired packages - apt update && apt install -y --no-install-recommends \ - # install git with git-lfs support - git git-lfs \ - # install python cmodule / binary module build utilities - python3-dev gcc make cmake cargo \ - # Configure global pip - && { \ - printf '%s\n' "[global]"; \ - printf '%s\n' "no-cache-dir = true"; \ - printf '%s\n' "disable-pip-version-check = true"; \ - } > /etc/pip.conf \ - # Create virtual environment for python-semantic-release - && python3 -m venv /psr/.venv \ - # Update core utilities in the virtual environment - && /psr/.venv/bin/pip install -U pip setuptools wheel \ - # Install psr & its dependencies from source into virtual environment - && /psr/.venv/bin/pip install /psr \ - # Cleanup - && apt clean -y - -ENV PSR_DOCKER_GITHUB_ACTION=true - -ENV PYTHONDONTWRITEBYTECODE=1 - -ENTRYPOINT ["/bin/bash", "-l", "/psr/action.sh"] diff --git a/MANIFEST.in b/MANIFEST.in index 9953cfd42..c6e688b9f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,5 +2,11 @@ graft src/**/data/ # include docs & testing into sdist, ignored for wheel build -graft tests/ graft docs/ +prune docs/_build/ +graft tests/ +prune tests/gh_action/ + +# Remove any generated files +prune **/__pycache__/ +prune src/*.egg-info diff --git a/action.yml b/action.yml index 7b9e3c654..2cf7cf5ec 100644 --- a/action.yml +++ b/action.yml @@ -140,4 +140,4 @@ outputs: runs: using: docker - image: Dockerfile + image: src/gh_action/Dockerfile diff --git a/src/gh_action/.dockerignore b/src/gh_action/.dockerignore new file mode 100644 index 000000000..e9783640d --- /dev/null +++ b/src/gh_action/.dockerignore @@ -0,0 +1,6 @@ +# Default, ignore everything +* + +# Except +!requirements.txt +!action.sh diff --git a/src/gh_action/Dockerfile b/src/gh_action/Dockerfile new file mode 100644 index 000000000..138b95a29 --- /dev/null +++ b/src/gh_action/Dockerfile @@ -0,0 +1,42 @@ +# This Dockerfile is only for GitHub Actions +FROM python:3.13-bookworm +ARG WORK_DIR="/opt/psr" + +WORKDIR ${WORK_DIR} + +ENV PSR_DOCKER_GITHUB_ACTION=true \ + PYTHONDONTWRITEBYTECODE=1 \ + PSR_VENV_BIN="${WORK_DIR}/.venv/bin" + +# Copy action utilities into container +COPY . ./ + +RUN \ + # Install desired packages + apt update && apt install -y --no-install-recommends \ + # install git with git-lfs support + git git-lfs \ + # install python cmodule / binary module build utilities + python3-dev gcc make cmake cargo \ + # Configure global pip + && { \ + printf '%s\n' "[global]"; \ + printf '%s\n' "disable-pip-version-check = true"; \ + } > /etc/pip.conf \ + # Create virtual environment for python-semantic-release + && python3 -m venv "$(dirname "${PSR_VENV_BIN}")" \ + # Update core utilities in the virtual environment + && "${PSR_VENV_BIN}/pip" install --upgrade pip setuptools wheel \ + # Install psr & its dependencies from source into virtual environment + && "${PSR_VENV_BIN}/pip" install --pre -r requirements.txt \ + # Validate binary availability + && bash -c "${PSR_VENV_BIN}/semantic-release --help" \ + # make action script executable + && chmod +x "${WORK_DIR}/action.sh" \ + # Put action script in PATH + && ln -s "${WORK_DIR}/action.sh" /usr/local/bin/action-entrypoint \ + # Clean up + && apt clean && rm -rf /var/lib/apt/lists/* \ + && find /tmp -mindepth 1 -delete + +ENTRYPOINT ["/usr/local/bin/action-entrypoint"] diff --git a/action.sh b/src/gh_action/action.sh similarity index 94% rename from action.sh rename to src/gh_action/action.sh index e70e39d4a..cd862d9a9 100644 --- a/action.sh +++ b/src/gh_action/action.sh @@ -3,7 +3,8 @@ set -e explicit_run_cmd() { - local cmd="$*" + local cmd="" + cmd="$(printf '%s' "$*" | sed 's/^ *//g' | sed 's/ *$//g')" printf '%s\n' "$> $cmd" eval "$cmd" } @@ -161,5 +162,8 @@ fi # Copy inputs into correctly-named environment variables export GH_TOKEN="${INPUT_GITHUB_TOKEN}" +# normalize extra spaces into single spaces as you combine the arguments +CMD_ARGS="$(printf '%s' "${ROOT_OPTIONS[*]} version ${ARGS[*]}" | sed 's/ [ ]*/ /g' | sed 's/^ *//g')" + # Run Semantic Release (explicitly use the GitHub action version) -explicit_run_cmd "/psr/.venv/bin/semantic-release ${ROOT_OPTIONS[*]} version ${ARGS[*]}" +explicit_run_cmd "$PSR_VENV_BIN/semantic-release $CMD_ARGS" diff --git a/src/gh_action/requirements.txt b/src/gh_action/requirements.txt new file mode 100644 index 000000000..1aae03384 --- /dev/null +++ b/src/gh_action/requirements.txt @@ -0,0 +1 @@ +python-semantic-release == 9.21.1 From 8d2b874dd43b24cbe57251581a938d28bb7171fa Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 16 May 2025 23:21:37 -0600 Subject: [PATCH 027/120] test(github-action): add github Docker action test suite --- .../gh_action/example_project/pyproject.toml | 4 + .../gh_action/example_project/releaserc.toml | 2 + tests/gh_action/run.sh | 133 ++++++++++++++++++ tests/gh_action/suite/test_version.sh | 86 +++++++++++ tests/gh_action/suite/test_version_strict.sh | 48 +++++++ tests/gh_action/utils.sh | 67 +++++++++ 6 files changed, 340 insertions(+) create mode 100644 tests/gh_action/example_project/pyproject.toml create mode 100644 tests/gh_action/example_project/releaserc.toml create mode 100644 tests/gh_action/run.sh create mode 100644 tests/gh_action/suite/test_version.sh create mode 100644 tests/gh_action/suite/test_version_strict.sh create mode 100644 tests/gh_action/utils.sh diff --git a/tests/gh_action/example_project/pyproject.toml b/tests/gh_action/example_project/pyproject.toml new file mode 100644 index 000000000..97a158c8d --- /dev/null +++ b/tests/gh_action/example_project/pyproject.toml @@ -0,0 +1,4 @@ +[project] +name = "example" +version = "0.0.0" +description = "Example project" diff --git a/tests/gh_action/example_project/releaserc.toml b/tests/gh_action/example_project/releaserc.toml new file mode 100644 index 000000000..c5c259a34 --- /dev/null +++ b/tests/gh_action/example_project/releaserc.toml @@ -0,0 +1,2 @@ +[semantic-release] +commit_parser = "emoji" diff --git a/tests/gh_action/run.sh b/tests/gh_action/run.sh new file mode 100644 index 000000000..3d92e0d88 --- /dev/null +++ b/tests/gh_action/run.sh @@ -0,0 +1,133 @@ +#!/bin/bash + +set -eu + +if ! command -v realpath &>/dev/null; then + realpath() { + readlink -f "$1" + } +fi + +TEST_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" +PROJ_DIR="$(realpath "$(dirname "$TEST_DIR")/..")" +EXAMPLE_PROJECT_BASE_DIR="${EXAMPLE_PROJECT_BASE_DIR:-"$TEST_DIR/example_project"}" + +if [ -z "${UTILS_LOADED:-}" ]; then + # shellcheck source=tests/utils.sh + source "$TEST_DIR/utils.sh" +fi + +create_example_project() { + local EXAMPLE_PROJECT_DIR="$1" + + log "Creating example project in: $EXAMPLE_PROJECT_DIR" + mkdir -vp "$(dirname "$EXAMPLE_PROJECT_DIR")" + cp -r "${EXAMPLE_PROJECT_BASE_DIR}" "$EXAMPLE_PROJECT_DIR" + + log "Constructing git history in repository" + pushd "$EXAMPLE_PROJECT_DIR" >/dev/null || return 1 + + # Initialize and configure git (remove any signature requirements) + git init + git config --local user.email "developer@users.noreply.github.com" + git config --local user.name "developer" + git config --local commit.gpgSign false + git config --local tag.gpgSign false + git remote add origin "https://github.com/python-semantic-release/example-project.git" + + # Create initial commit and tag + git add . + git commit -m "Initial commit" + + # set default branch to main + git branch -m main + + # Create the first release (with commit & tag) + cat <pyproject.toml +[project] +name = "example" +version = "1.0.0" +description = "Example project" +EOF + git commit -am '1.0.0' + git tag -a v1.0.0 -m "v1.0.0" + + popd >/dev/null || return 1 + log "Example project created successfully" +} + +# ------------------------------ +# TEST SUITE DRIVER +# ------------------------------ + +run_test_suite() { + local ALL_TEST_FNS + + # Dynamically import all test scripts + for test_script in "$TEST_DIR"/suite/test_*.sh; do + if [ -f "$test_script" ]; then + if ! source "$test_script"; then + error "Failed to load test script: $test_script" + fi + fi + done + + # Extract all test functions + tests_in_env="$(compgen -A function | grep "^test_")" + read -r -a ALL_TEST_FNS <<< "$(printf '%s' "$tests_in_env" | tr '\n' ' ')" + + log "" + log "************************" + log "* Running test suite *" + log "************************" + + # Incrementally run all test functions and flag if any fail + local test_index=1 + local test_failures=0 + for test_fn in "${ALL_TEST_FNS[@]}"; do + if command -v "$test_fn" &>/dev/null; then + if ! "$test_fn" "$test_index"; then + ((test_failures++)) + fi + fi + log "--------------------------------------------------------------------------------" + ((test_index++)) + done + + log "" + log "************************" + log "* Test Summary *" + log "************************" + log "" + log "Total tests executed: ${#ALL_TEST_FNS[@]}" + log "Successes: $((${#ALL_TEST_FNS[@]} - test_failures))" + log "Failures: $test_failures" + + if [ "$test_failures" -gt 0 ]; then + return 1 + fi +} + +# ------------------------------ +# MAIN +# ------------------------------ + +log "================================================================================" +log "|| PSR Version Action Test Runner ||" +log "================================================================================" +log "Initializing..." + +# Make absolute path to project directory +PROJECT_MOUNT_DIR="${PROJ_DIR:?}/${PROJECT_MOUNT_DIR:?}" + +log "" +log "******************************" +log "* Running test suite setup *" +log "******************************" +log "" + +# Setup project environment +create_example_project "$PROJECT_MOUNT_DIR" +trap 'rm -rf "${PROJECT_MOUNT_DIR:?}"' EXIT + +run_test_suite diff --git a/tests/gh_action/suite/test_version.sh b/tests/gh_action/suite/test_version.sh new file mode 100644 index 000000000..a853bf99f --- /dev/null +++ b/tests/gh_action/suite/test_version.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +__file__="$(realpath "${BASH_SOURCE[0]}")" +__directory__="$(dirname "${__file__}")" + +if ! [ "${UTILS_LOADED}" = "true" ]; then + # shellcheck source=tests/utils.sh + source "$__directory__/../utils.sh" +fi + +test_version() { + # Using default configuration within PSR with no modifications + # triggering the NOOP mode to prevent errors since the repo doesn't exist + # We are just trying to test that the root options & tag arguments are + # passed to the action without a fatal error + local index="${1:?Index not provided}" + local test_name="${FUNCNAME[0]}" + + # Create expectations & set env variables that will be passed in for Docker command + local WITH_VAR_GITHUB_TOKEN="ghp_1x2x3x4x5x6x7x8x9x0x1x2x3x4x5x6x7x8x9x0" + local WITH_VAR_NO_OPERATION_MODE="true" + local WITH_VAR_VERBOSITY="2" + local expected_psr_cmd=".*/bin/semantic-release -vv --noop version" + + # Execute the test & capture output + # Fatal errors if exit code is not 0 + local output="" + if ! output="$(run_test "$index. $test_name" 2>&1)"; then + # Log the output for debugging purposes + log "$output" + error "fatal error occurred!" + error "::error:: $test_name failed!" + return 1 + fi + + # Evaluate the output to ensure the expected command is present + if ! printf '%s' "$output" | grep -q -E "$expected_psr_cmd"; then + # Log the output for debugging purposes + log "$output" + error "Failed to find the expected command in the output!" + error "\tExpected Command: $expected_psr_cmd" + error "::error:: $test_name failed!" + return 1 + fi + + log "\n$index. $test_name: PASSED!" +} + +test_version_w_custom_config() { + # Using default configuration within PSR with no modifications + # triggering the NOOP mode to prevent errors since the repo doesn't exist + # We are just trying to test that the root options & tag arguments are + # passed to the action without a fatal error + local index="${1:?Index not provided}" + local test_name="${FUNCNAME[0]}" + + # Create expectations & set env variables that will be passed in for Docker command + local WITH_VAR_GITHUB_TOKEN="ghp_1x2x3x4x5x6x7x8x9x0x1x2x3x4x5x6x7x8x9x0" + local WITH_VAR_NO_OPERATION_MODE="true" + local WITH_VAR_VERBOSITY="0" + local WITH_VAR_CONFIG_FILE="releaserc.toml" + local expected_psr_cmd=".*/bin/semantic-release --config $WITH_VAR_CONFIG_FILE --noop version" + + # Execute the test & capture output + # Fatal errors if exit code is not 0 + local output="" + if ! output="$(run_test "$index. $test_name" 2>&1)"; then + # Log the output for debugging purposes + log "$output" + error "fatal error occurred!" + error "::error:: $test_name failed!" + return 1 + fi + + # Evaluate the output to ensure the expected command is present + if ! printf '%s' "$output" | grep -q "$expected_psr_cmd"; then + # Log the output for debugging purposes + log "$output" + error "Failed to find the expected command in the output!" + error "\tExpected Command: $expected_psr_cmd" + error "::error:: $test_name failed!" + return 1 + fi + + log "\n$index. $test_name: PASSED!" +} diff --git a/tests/gh_action/suite/test_version_strict.sh b/tests/gh_action/suite/test_version_strict.sh new file mode 100644 index 000000000..cd9a2b512 --- /dev/null +++ b/tests/gh_action/suite/test_version_strict.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +__file__="$(realpath "${BASH_SOURCE[0]}")" +__directory__="$(dirname "${__file__}")" + +if ! [ "${UTILS_LOADED:-false}" = "true" ]; then + # shellcheck source=tests/utils.sh + source "$__directory__/../utils.sh" +fi + +test_version_strict() { + # Using default configuration within PSR with no modifications + # triggering the NOOP mode to prevent errors since the repo doesn't exist + # We are just trying to test that the root options & tag arguments are + # passed to the action without a fatal error + local index="${1:?Index not provided}" + local test_name="${FUNCNAME[0]}" + + # Create expectations & set env variables that will be passed in for Docker command + local WITH_VAR_GITHUB_TOKEN="ghp_1x2x3x4x5x6x7x8x9x0x1x2x3x4x5x6x7x8x9x0" + local WITH_VAR_NO_OPERATION_MODE="true" + local WITH_VAR_STRICT="true" + local expected_psr_cmd=".*/bin/semantic-release -v --strict --noop version" + # Since the example project is at the latest release, we expect strict mode + # to fail with a non-zero exit code + + # Execute the test & capture output + local output="" + if output="$(run_test "$index. $test_name" 2>&1)"; then + # Log the output for debugging purposes + log "$output" + error "Strict mode should of exited with a non-zero exit code but didn't!" + error "::error:: $test_name failed!" + return 1 + fi + + # Evaluate the output to ensure the expected command is present + if ! printf '%s' "$output" | grep -q "$expected_psr_cmd"; then + # Log the output for debugging purposes + log "$output" + error "Failed to find the expected command in the output!" + error "\tExpected Command: $expected_psr_cmd" + error "::error:: $test_name failed!" + return 1 + fi + + log "\n$index. $test_name: PASSED!" +} \ No newline at end of file diff --git a/tests/gh_action/utils.sh b/tests/gh_action/utils.sh new file mode 100644 index 000000000..a08f25e73 --- /dev/null +++ b/tests/gh_action/utils.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# ------------------------------ +# UTILS +# ------------------------------ +IMAGE_TAG="${TEST_CONTAINER_TAG:?TEST_CONTAINER_TAG not set}" +PROJECT_MOUNT_DIR="${PROJECT_MOUNT_DIR:-"tmp/project"}" +GITHUB_ACTIONS_CWD="/github/workspace" + +log() { + printf '%b\n' "$*" +} + +error() { + log >&2 "\033[31m$*\033[0m" +} + +explicit_run_cmd() { + local cmd="$*" + log "$> $cmd\n" + eval "$cmd" +} + +run_test() { + local test_name="${1:?Test name not provided}" + test_name="${test_name//_/ }" + test_name="$(tr "[:lower:]" "[:upper:]" <<< "${test_name:0:1}")${test_name:1}" + + # Set Defaults based on action.yml + [ -z "${WITH_VAR_DIRECTORY:-}" ] && local WITH_VAR_DIRECTORY="." + [ -z "${WITH_VAR_CONFIG_FILE:-}" ] && local WITH_VAR_CONFIG_FILE="" + [ -z "${WITH_VAR_NO_OPERATION_MODE:-}" ] && local WITH_VAR_NO_OPERATION_MODE="false" + [ -z "${WITH_VAR_VERBOSITY:-}" ] && local WITH_VAR_VERBOSITY="1" + + # Extract all WITH_VAR_ variables dynamically from environment + local ENV_ARGS=() + args_in_env="$(compgen -A variable | grep "^WITH_VAR_")" + read -r -a ENV_ARGS <<< "$(printf '%s' "$args_in_env" | tr '\n' ' ')" + + # Set Docker arguments (default: always remove the container after execution) + local DOCKER_ARGS=("--rm") + + # Add all WITH_VAR_ variables to the Docker command + local actions_input_var_name="" + for input in "${ENV_ARGS[@]}"; do + # Convert WITH_VAR_ to INPUT_ to simulate GitHub Actions input syntax + actions_input_var_name="INPUT_${input#WITH_VAR_}" + + # Add the environment variable to the Docker command + DOCKER_ARGS+=("-e ${actions_input_var_name}='${!input}'") + done + + # Add the project directory to the Docker command + DOCKER_ARGS+=("-v ${PROJECT_MOUNT_DIR}:${GITHUB_ACTIONS_CWD}") + + # Set the working directory to the project directory + DOCKER_ARGS+=("-w ${GITHUB_ACTIONS_CWD}") + + # Run the test + log "\n$test_name" + log "--------------------------------------------------------------------------------" + if ! explicit_run_cmd "docker run ${DOCKER_ARGS[*]} $IMAGE_TAG"; then + return 1 + fi +} + +export UTILS_LOADED="true" From d664428e8aa7261b08b9aab526d35312d6e4b2e4 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 16 May 2025 23:23:01 -0600 Subject: [PATCH 028/120] ci(validate): add integration testing of PSR github docker action --- .github/changed-files-spec.yml | 13 +++++--- .github/workflows/ci.yml | 10 ++++-- .github/workflows/cicd.yml | 10 ++++-- .github/workflows/manual.yml | 2 ++ .github/workflows/validate.yml | 60 ++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 8 deletions(-) diff --git a/.github/changed-files-spec.yml b/.github/changed-files-spec.yml index 7f8e40353..8d479d1db 100644 --- a/.github/changed-files-spec.yml +++ b/.github/changed-files-spec.yml @@ -2,8 +2,6 @@ build: - MANIFEST.in - - Dockerfile - - .dockerignore - scripts/** docs: - docs/** @@ -11,8 +9,15 @@ docs: - AUTHORS.rst - CONTRIBUTING.rst - CHANGELOG.rst +gha_src: + - src/gh_action/** src: - - src/** + - src/semantic_release/** - pyproject.toml +gha_tests: + - tests/gh_action/** tests: - - tests/** + - tests/e2e/** + - tests/fixtures/** + - tests/unit/** + - tests/*.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53d5c83c0..8658260eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,8 +62,10 @@ jobs: [ "${{ steps.ci-changed-files.outputs.ci_any_changed }}" == "true" ] || \ [ "${{ steps.core-changed-files.outputs.docs_any_changed }}" == "true" ] || \ [ "${{ steps.core-changed-files.outputs.src_any_changed }}" == "true" ] || \ - [ "${{ steps.core-changed-files.outputs.tests_any_changed }}" == "true" ]; then - printf '%s\n' "any_changed=true" >> $GITHUB_OUTPUT + [ "${{ steps.core-changed-files.outputs.tests_any_changed }}" == "true" ] || \ + [ "${{ steps.core-changed-files.outputs.gha_src_any_changed }}" == "true" ] || \ + [ "${{ steps.core-changed-files.outputs.gha_tests_any_changed }}" == "true" ]; then + printf '%s\n' "any_changed=true" >> $GITHUB_OUTPUT fi outputs: @@ -74,6 +76,8 @@ jobs: doc-changes: ${{ steps.core-changed-files.outputs.docs_any_changed }} src-changes: ${{ steps.core-changed-files.outputs.src_any_changed }} test-changes: ${{ steps.core-changed-files.outputs.tests_any_changed }} + gha-src-changes: ${{ steps.core-changed-files.outputs.gha_src_any_changed }} + gha-test-changes: ${{ steps.core-changed-files.outputs.gha_tests_any_changed }} validate: @@ -91,5 +95,7 @@ jobs: doc-files-changed: ${{ needs.eval-changes.outputs.doc-changes }} src-files-changed: ${{ needs.eval-changes.outputs.src-changes }} test-files-changed: ${{ needs.eval-changes.outputs.test-changes }} + gha-src-files-changed: ${{ needs.eval-changes.outputs.gha-src-changes }} + gha-test-files-changed: ${{ needs.eval-changes.outputs.gha-test-changes }} permissions: {} secrets: {} diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 13dc13bda..358dff3ab 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -49,8 +49,10 @@ jobs: [ "${{ steps.ci-changed-files.outputs.ci_any_changed }}" == "true" ] || \ [ "${{ steps.core-changed-files.outputs.docs_any_changed }}" == "true" ] || \ [ "${{ steps.core-changed-files.outputs.src_any_changed }}" == "true" ] || \ - [ "${{ steps.core-changed-files.outputs.tests_any_changed }}" == "true" ]; then - printf '%s\n' "any_changed=true" >> $GITHUB_OUTPUT + [ "${{ steps.core-changed-files.outputs.tests_any_changed }}" == "true" ] || \ + [ "${{ steps.core-changed-files.outputs.gha_src_any_changed }}" == "true" ] || \ + [ "${{ steps.core-changed-files.outputs.gha_tests_any_changed }}" == "true" ]; then + printf '%s\n' "any_changed=true" >> $GITHUB_OUTPUT fi outputs: @@ -60,6 +62,8 @@ jobs: doc-changes: ${{ steps.core-changed-files.outputs.docs_any_changed }} src-changes: ${{ steps.core-changed-files.outputs.src_any_changed }} test-changes: ${{ steps.core-changed-files.outputs.tests_any_changed }} + gha-src-changes: ${{ steps.core-changed-files.outputs.gha_src_any_changed }} + gha-test-changes: ${{ steps.core-changed-files.outputs.gha_tests_any_changed }} validate: @@ -77,6 +81,8 @@ jobs: doc-files-changed: ${{ needs.eval-changes.outputs.doc-changes }} src-files-changed: ${{ needs.eval-changes.outputs.src-changes }} test-files-changed: ${{ needs.eval-changes.outputs.test-changes }} + gha-src-files-changed: ${{ needs.eval-changes.outputs.gha-src-changes }} + gha-test-files-changed: ${{ needs.eval-changes.outputs.gha-test-changes }} permissions: {} secrets: {} diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index 64263f37e..e1f292d66 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -133,6 +133,8 @@ jobs: doc-files-changed: true src-files-changed: true test-files-changed: true + gha-src-files-changed: true + gha-test-files-changed: true files-changed: true permissions: {} secrets: {} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 08cbb4c91..e17483728 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -43,6 +43,16 @@ on: type: string required: false default: 'false' + gha-src-files-changed: + description: 'Boolean string result for if GitHub Action source files have changed' + type: string + required: false + default: 'false' + gha-test-files-changed: + description: 'Boolean string result for if GitHub Action test files have changed' + type: string + required: false + default: 'false' outputs: new-release-detected: description: Boolean string result for if new release is available @@ -373,6 +383,56 @@ jobs: annotate_only: true + test-gh-action: + name: Validate Action Build & Execution + runs-on: ubuntu-latest + if: inputs.gha-src-files-changed == 'true' || inputs.gha-test-files-changed == 'true' || inputs.ci-files-changed == 'true' + + needs: + - build + - unit-test + + env: + TEST_CONTAINER_TAG: psr-action:latest + ACTION_SRC_DIR: src/gh_action + + steps: + - name: Setup | Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 1 + ref: ${{ github.sha }} + + - name: Setup | Download Distribution Artifacts + uses: actions/download-artifact@v4 + with: + name: ${{ needs.build.outputs.distribution-artifacts }} + path: ${{ env.ACTION_SRC_DIR }} + + - name: Setup | Update Dependency list with latest version + working-directory: ${{ env.ACTION_SRC_DIR }} + run: | + find . -name '*.whl' > requirements.txt + + - name: Setup | Allow Docker build to include wheel files + working-directory: ${{ env.ACTION_SRC_DIR }} + run: | + printf '%s\n' "!*.whl" >> .dockerignore + + - name: Build | Action Container + id: container-builder + uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 + with: + context: ${{ env.ACTION_SRC_DIR }} + load: true # add to `docker images` + push: false + platforms: linux/amd64 + tags: ${{ env.TEST_CONTAINER_TAG }} + + - name: Test | Action Container + run: bash tests/gh_action/run.sh + + lint: name: Lint if: ${{ inputs.files-changed == 'true' }} From 5c5ca2aaf8ecce443546bf1337755069a0b2f442 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 12:31:31 -0600 Subject: [PATCH 029/120] chore(conf): configure project to stamp the ci action's `requirements.txt` w/ new version (#1248) --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ca6945374..7c1ae30c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -412,6 +412,9 @@ build_command = """ python -m build . """ major_on_zero = true +version_variables = [ + "src/gh_action/requirements.txt:python-semantic-release:nf", +] version_toml = ["pyproject.toml:project.version"] [tool.semantic_release.changelog] From 726026be58de046e09188b50315ff19dadef71c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 23:28:11 -0700 Subject: [PATCH 030/120] ci(deps): bump `docker/build-push-action@v6.16.0` to `v6.17.0` (#1250) --- .github/workflows/validate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index e17483728..e5a9b842d 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -421,7 +421,7 @@ jobs: - name: Build | Action Container id: container-builder - uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 + uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 with: context: ${{ env.ACTION_SRC_DIR }} load: true # add to `docker images` From ed5a2da7fbed42f2fdcdf6a002aae80466300a26 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 13:46:29 -0600 Subject: [PATCH 031/120] refactor(changelog-templates): move templates from `angular/` to `conventional/` --- docs/changelog_templates.rst | 2 +- src/semantic_release/cli/config.py | 7 ++--- src/semantic_release/commit_parser/emoji.py | 2 +- .../md/.components/changelog_header.md.j2 | 0 .../md/.components/changelog_init.md.j2 | 0 .../md/.components/changelog_update.md.j2 | 0 .../md/.components/changes.md.j2 | 0 .../md/.components/first_release.md.j2 | 0 .../md/.components/macros.md.j2 | 0 .../md/.components/unreleased_changes.md.j2 | 0 .../md/.components/versioned_changes.md.j2 | 0 .../md/.release_notes.md.j2 | 0 .../md/CHANGELOG.md.j2 | 0 .../rst/.components/changelog_header.rst.j2 | 0 .../rst/.components/changelog_init.rst.j2 | 0 .../rst/.components/changelog_update.rst.j2 | 0 .../rst/.components/changes.rst.j2 | 0 .../rst/.components/first_release.rst.j2 | 0 .../rst/.components/macros.rst.j2 | 0 .../rst/.components/unreleased_changes.rst.j2 | 0 .../rst/.components/versioned_changes.rst.j2 | 0 .../rst/CHANGELOG.rst.j2 | 0 tests/fixtures/example_project.py | 4 +-- .../changelog/test_default_changelog.py | 18 +++++------ .../changelog/test_release_notes.py | 30 +++++++++---------- 25 files changed, 31 insertions(+), 32 deletions(-) rename src/semantic_release/data/templates/{angular => conventional}/md/.components/changelog_header.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/md/.components/changelog_init.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/md/.components/changelog_update.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/md/.components/changes.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/md/.components/first_release.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/md/.components/macros.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/md/.components/unreleased_changes.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/md/.components/versioned_changes.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/md/.release_notes.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/md/CHANGELOG.md.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/rst/.components/changelog_header.rst.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/rst/.components/changelog_init.rst.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/rst/.components/changelog_update.rst.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/rst/.components/changes.rst.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/rst/.components/first_release.rst.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/rst/.components/macros.rst.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/rst/.components/unreleased_changes.rst.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/rst/.components/versioned_changes.rst.j2 (100%) rename src/semantic_release/data/templates/{angular => conventional}/rst/CHANGELOG.rst.j2 (100%) diff --git a/docs/changelog_templates.rst b/docs/changelog_templates.rst index 1b6e5fc7d..e950db3d1 100644 --- a/docs/changelog_templates.rst +++ b/docs/changelog_templates.rst @@ -331,7 +331,7 @@ be copied to its target location without being rendered by the template engine. When initially starting out at customizing your own changelog templates, you should reference the default template embedded within PSR. The template directory is located at ``data/templates/`` within the PSR package. Within our templates - directory we separate out each type of commit parser (e.g. angular) and the + directory we separate out each type of commit parser (e.g. conventional) and the content format type (e.g. markdown). You can copy this directory to your repository's templates directory and then customize the templates to your liking. diff --git a/src/semantic_release/cli/config.py b/src/semantic_release/cli/config.py index 41ac02058..be8ad4a5e 100644 --- a/src/semantic_release/cli/config.py +++ b/src/semantic_release/cli/config.py @@ -859,11 +859,11 @@ def from_raw_config( # noqa: C901 # Here we just assume the desired changelog style matches the parser name # as we provide templates specific to each parser type. Unfortunately if the user has # provided a custom parser, it would be up to the user to provide custom templates - # but we just assume the base template is angular + # but we just assume the base template is conventional # changelog_style = ( # raw.commit_parser # if raw.commit_parser in _known_commit_parsers - # else "angular" + # else "conventional" # ) self = cls( @@ -887,8 +887,7 @@ def from_raw_config( # noqa: C901 changelog_excluded_commit_patterns=changelog_excluded_commit_patterns, # TODO: change when we have other styles per parser # changelog_style=changelog_style, - # TODO: Breaking Change v10, change to conventional - changelog_style="angular", + changelog_style="conventional", changelog_output_format=raw.changelog.default_templates.output_format, prerelease=branch_config.prerelease, ignore_token_for_push=raw.remote.ignore_token_for_push, diff --git a/src/semantic_release/commit_parser/emoji.py b/src/semantic_release/commit_parser/emoji.py index bb830b395..5be4f1ec3 100644 --- a/src/semantic_release/commit_parser/emoji.py +++ b/src/semantic_release/commit_parser/emoji.py @@ -450,7 +450,7 @@ def _find_squashed_commits_in_str(self, message: str) -> list[str]: if not clean_paragraph.strip(): continue - # Check if the paragraph is the start of a new angular commit + # Check if the paragraph is the start of a new emoji commit if not self.emoji_selector.search(clean_paragraph): if not separate_commit_msgs and not current_msg: # if there are no separate commit messages and no current message diff --git a/src/semantic_release/data/templates/angular/md/.components/changelog_header.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/changelog_header.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/.components/changelog_header.md.j2 rename to src/semantic_release/data/templates/conventional/md/.components/changelog_header.md.j2 diff --git a/src/semantic_release/data/templates/angular/md/.components/changelog_init.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/changelog_init.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/.components/changelog_init.md.j2 rename to src/semantic_release/data/templates/conventional/md/.components/changelog_init.md.j2 diff --git a/src/semantic_release/data/templates/angular/md/.components/changelog_update.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/changelog_update.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/.components/changelog_update.md.j2 rename to src/semantic_release/data/templates/conventional/md/.components/changelog_update.md.j2 diff --git a/src/semantic_release/data/templates/angular/md/.components/changes.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/changes.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/.components/changes.md.j2 rename to src/semantic_release/data/templates/conventional/md/.components/changes.md.j2 diff --git a/src/semantic_release/data/templates/angular/md/.components/first_release.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/first_release.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/.components/first_release.md.j2 rename to src/semantic_release/data/templates/conventional/md/.components/first_release.md.j2 diff --git a/src/semantic_release/data/templates/angular/md/.components/macros.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/.components/macros.md.j2 rename to src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 diff --git a/src/semantic_release/data/templates/angular/md/.components/unreleased_changes.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/unreleased_changes.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/.components/unreleased_changes.md.j2 rename to src/semantic_release/data/templates/conventional/md/.components/unreleased_changes.md.j2 diff --git a/src/semantic_release/data/templates/angular/md/.components/versioned_changes.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/versioned_changes.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/.components/versioned_changes.md.j2 rename to src/semantic_release/data/templates/conventional/md/.components/versioned_changes.md.j2 diff --git a/src/semantic_release/data/templates/angular/md/.release_notes.md.j2 b/src/semantic_release/data/templates/conventional/md/.release_notes.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/.release_notes.md.j2 rename to src/semantic_release/data/templates/conventional/md/.release_notes.md.j2 diff --git a/src/semantic_release/data/templates/angular/md/CHANGELOG.md.j2 b/src/semantic_release/data/templates/conventional/md/CHANGELOG.md.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/md/CHANGELOG.md.j2 rename to src/semantic_release/data/templates/conventional/md/CHANGELOG.md.j2 diff --git a/src/semantic_release/data/templates/angular/rst/.components/changelog_header.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/changelog_header.rst.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/rst/.components/changelog_header.rst.j2 rename to src/semantic_release/data/templates/conventional/rst/.components/changelog_header.rst.j2 diff --git a/src/semantic_release/data/templates/angular/rst/.components/changelog_init.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/changelog_init.rst.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/rst/.components/changelog_init.rst.j2 rename to src/semantic_release/data/templates/conventional/rst/.components/changelog_init.rst.j2 diff --git a/src/semantic_release/data/templates/angular/rst/.components/changelog_update.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/changelog_update.rst.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/rst/.components/changelog_update.rst.j2 rename to src/semantic_release/data/templates/conventional/rst/.components/changelog_update.rst.j2 diff --git a/src/semantic_release/data/templates/angular/rst/.components/changes.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/changes.rst.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/rst/.components/changes.rst.j2 rename to src/semantic_release/data/templates/conventional/rst/.components/changes.rst.j2 diff --git a/src/semantic_release/data/templates/angular/rst/.components/first_release.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/first_release.rst.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/rst/.components/first_release.rst.j2 rename to src/semantic_release/data/templates/conventional/rst/.components/first_release.rst.j2 diff --git a/src/semantic_release/data/templates/angular/rst/.components/macros.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/rst/.components/macros.rst.j2 rename to src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 diff --git a/src/semantic_release/data/templates/angular/rst/.components/unreleased_changes.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/unreleased_changes.rst.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/rst/.components/unreleased_changes.rst.j2 rename to src/semantic_release/data/templates/conventional/rst/.components/unreleased_changes.rst.j2 diff --git a/src/semantic_release/data/templates/angular/rst/.components/versioned_changes.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/versioned_changes.rst.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/rst/.components/versioned_changes.rst.j2 rename to src/semantic_release/data/templates/conventional/rst/.components/versioned_changes.rst.j2 diff --git a/src/semantic_release/data/templates/angular/rst/CHANGELOG.rst.j2 b/src/semantic_release/data/templates/conventional/rst/CHANGELOG.rst.j2 similarity index 100% rename from src/semantic_release/data/templates/angular/rst/CHANGELOG.rst.j2 rename to src/semantic_release/data/templates/conventional/rst/CHANGELOG.rst.j2 diff --git a/tests/fixtures/example_project.py b/tests/fixtures/example_project.py index 7575ec96f..5d0c60886 100644 --- a/tests/fixtures/example_project.py +++ b/tests/fixtures/example_project.py @@ -248,7 +248,7 @@ def default_changelog_md_template() -> Path: return Path( str( files(semantic_release.__name__).joinpath( - Path("data", "templates", "angular", "md", "CHANGELOG.md.j2") + Path("data", "templates", "conventional", "md", "CHANGELOG.md.j2") ) ) ).resolve() @@ -260,7 +260,7 @@ def default_changelog_rst_template() -> Path: return Path( str( files(semantic_release.__name__).joinpath( - Path("data", "templates", "angular", "rst", "CHANGELOG.rst.j2") + Path("data", "templates", "conventional", "rst", "CHANGELOG.rst.j2") ) ) ).resolve() diff --git a/tests/unit/semantic_release/changelog/test_default_changelog.py b/tests/unit/semantic_release/changelog/test_default_changelog.py index adbbdc241..6e42f7fad 100644 --- a/tests/unit/semantic_release/changelog/test_default_changelog.py +++ b/tests/unit/semantic_release/changelog/test_default_changelog.py @@ -23,7 +23,7 @@ def default_changelog_template() -> str: """Retrieve the semantic-release default changelog template.""" version_notes_template = files(semantic_release.__name__).joinpath( - Path("data", "templates", "angular", "md", "CHANGELOG.md.j2") + Path("data", "templates", "conventional", "md", "CHANGELOG.md.j2") ) return version_notes_template.read_text(encoding="utf-8") @@ -112,7 +112,7 @@ def test_default_changelog_template( insertion_flag="", mask_initial_release=True, ), - changelog_style="angular", + changelog_style="conventional", ) assert expected_changelog == actual_changelog @@ -235,7 +235,7 @@ def test_default_changelog_template_w_a_brk_change( insertion_flag="", mask_initial_release=True, ), - changelog_style="angular", + changelog_style="conventional", ) assert expected_changelog == actual_changelog @@ -381,7 +381,7 @@ def test_default_changelog_template_w_multiple_brk_changes( insertion_flag="", mask_initial_release=True, ), - changelog_style="angular", + changelog_style="conventional", ) assert expected_changelog == actual_changelog @@ -475,7 +475,7 @@ def test_default_changelog_template_no_initial_release_mask( insertion_flag="", mask_initial_release=False, ), - changelog_style="angular", + changelog_style="conventional", ) assert expected_changelog == actual_changelog @@ -572,7 +572,7 @@ def test_default_changelog_template_w_unreleased_changes( insertion_flag="", mask_initial_release=True, ), - changelog_style="angular", + changelog_style="conventional", ) assert expected_changelog == actual_changelog @@ -695,7 +695,7 @@ def test_default_changelog_template_w_a_notice( insertion_flag="", mask_initial_release=False, ), - changelog_style="angular", + changelog_style="conventional", ) assert expected_changelog == actual_changelog @@ -855,7 +855,7 @@ def test_default_changelog_template_w_a_notice_n_brk_change( insertion_flag="", mask_initial_release=False, ), - changelog_style="angular", + changelog_style="conventional", ) assert expected_changelog == actual_changelog @@ -1019,7 +1019,7 @@ def test_default_changelog_template_w_multiple_notices( insertion_flag="", mask_initial_release=False, ), - changelog_style="angular", + changelog_style="conventional", ) assert expected_changelog == actual_changelog diff --git a/tests/unit/semantic_release/changelog/test_release_notes.py b/tests/unit/semantic_release/changelog/test_release_notes.py index 2b95ec827..02f217bf1 100644 --- a/tests/unit/semantic_release/changelog/test_release_notes.py +++ b/tests/unit/semantic_release/changelog/test_release_notes.py @@ -26,7 +26,7 @@ def release_notes_template() -> str: """Retrieve the semantic-release default release notes template.""" version_notes_template = files(semantic_release.__name__).joinpath( - Path("data", "templates", "angular", "md", ".release_notes.md.j2") + Path("data", "templates", "conventional", "md", ".release_notes.md.j2") ) return version_notes_template.read_text(encoding="utf-8") @@ -156,7 +156,7 @@ def test_default_release_notes_template( release=release, template_dir=Path(""), history=artificial_release_history, - style="angular", + style="conventional", mask_initial_release=mask_initial_release, license_name=license_name, ) @@ -248,7 +248,7 @@ def test_default_release_notes_template_w_a_brk_description( release=release, template_dir=Path(""), history=release_history_w_brk_change, - style="angular", + style="conventional", mask_initial_release=mask_initial_release, ) @@ -369,7 +369,7 @@ def test_default_release_notes_template_w_multiple_brk_changes( release=release, template_dir=Path(""), history=release_history_w_multiple_brk_changes, - style="angular", + style="conventional", mask_initial_release=mask_initial_release, ) @@ -417,7 +417,7 @@ def test_default_release_notes_template_first_release_masked( release=release, template_dir=Path(""), history=single_release_history, - style="angular", + style="conventional", mask_initial_release=True, license_name=license_name, ) @@ -481,7 +481,7 @@ def test_default_release_notes_template_first_release_unmasked( release=release, template_dir=Path(""), history=single_release_history, - style="angular", + style="conventional", mask_initial_release=False, license_name=license_name, ) @@ -529,7 +529,7 @@ def test_release_notes_context_sort_numerically_filter( release=release, template_dir=example_project_dir, history=single_release_history, - style="angular", + style="conventional", mask_initial_release=False, ) @@ -576,7 +576,7 @@ def test_release_notes_context_sort_numerically_filter_reversed( release=release, template_dir=example_project_dir, history=single_release_history, - style="angular", + style="conventional", mask_initial_release=False, ) @@ -603,7 +603,7 @@ def test_release_notes_context_pypi_url_filter( release=release, template_dir=example_project_dir, history=single_release_history, - style="angular", + style="conventional", mask_initial_release=False, ) @@ -630,7 +630,7 @@ def test_release_notes_context_pypi_url_filter_tagged( release=release, template_dir=example_project_dir, history=single_release_history, - style="angular", + style="conventional", mask_initial_release=False, ) @@ -675,7 +675,7 @@ def test_release_notes_context_release_url_filter( release=release, template_dir=example_project_dir, history=single_release_history, - style="angular", + style="conventional", mask_initial_release=False, ) @@ -718,7 +718,7 @@ def test_release_notes_context_format_w_official_name_filter( release=release, template_dir=example_project_dir, history=single_release_history, - style="angular", + style="conventional", mask_initial_release=False, ) @@ -807,7 +807,7 @@ def test_default_release_notes_template_w_a_notice( release=release, template_dir=Path(""), history=release_history_w_a_notice, - style="angular", + style="conventional", mask_initial_release=mask_initial_release, ) @@ -928,7 +928,7 @@ def test_default_release_notes_template_w_a_notice_n_brk_change( release=release, template_dir=Path(""), history=release_history_w_notice_n_brk_change, - style="angular", + style="conventional", mask_initial_release=mask_initial_release, ) @@ -1041,7 +1041,7 @@ def test_default_release_notes_template_w_multiple_notices( release=release, template_dir=Path(""), history=release_history_w_multiple_notices, - style="angular", + style="conventional", mask_initial_release=mask_initial_release, ) From e7ac155a91fc2e735d3cbf9b66fb4e5ff40a1466 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Thu, 19 Dec 2024 23:44:58 -0700 Subject: [PATCH 032/120] fix(changelog-md)!: change to 1-line descriptions in markdown template Remove the code that writes out the rest of the commit message from the default template. BREAKING CHANGE: The default Markdown changelog template and release notes template will no longer print out the entire commit message contents, instead, it will only print the commit subject line. This comes to meet the high demand of better formatted changelogs and requests for subject line only. Originally, it was a decision to not hide commit subjects that were included in the commit body via the `git merge --squash` command and PSR did not have another alternative. At this point, all the built-in parsers have the ability to parse squashed commits and separate them out into their own entry on the changelog. Therefore, the default template no longer needs to write out the full commit body. See the commit parser options if you want to enable/disable parsing squash commits. Resolves: #733 --- .../conventional/md/.components/changes.md.j2 | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/semantic_release/data/templates/conventional/md/.components/changes.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/changes.md.j2 index b2b89ff12..c81b0faa1 100644 --- a/src/semantic_release/data/templates/conventional/md/.components/changes.md.j2 +++ b/src/semantic_release/data/templates/conventional/md/.components/changes.md.j2 @@ -46,16 +46,8 @@ EXAMPLE: #}{% set commit_descriptions = [] %}{# #}{% for commit in ns.commits -%}{# # Update the first line with reference links and if commit description - # has more than one line, add the rest of the lines - # NOTE: This is specifically to make sure to not hide contents - # of squash commits (until parse support is added) +%}{# # Add reference links to the commit summary line #}{% set description = "- %s" | format(format_commit_summary_line(commit)) -%}{% if commit.descriptions | length > 1 -%}{% set description = "%s\n\n%s" | format( - description, commit.descriptions[1:] | join("\n\n") - ) -%}{% endif %}{% set description = description | autofit_text_width(max_line_width, hanging_indent) %}{% set _ = commit_descriptions.append(description) %}{% endfor From 731466fec4e06fe71f6c4addd4ae2ec2182ae9c1 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Thu, 19 Dec 2024 23:45:24 -0700 Subject: [PATCH 033/120] fix(changelog-rst)!: change to 1-line descriptions in the default ReStructuredText template Remove the code that writes out the rest of the commit message from the default template. BREAKING CHANGE: The default ReStructured changelog template will no longer print out the entire commit message contents, instead, it will only print the commit subject line. This comes to meet the high demand of better formatted changelogs and requests for subject line only. Originally, it was a decision to not hide commit subjects that were included in the commit body via the `git merge --squash` command and PSR did not have another alternative. At this point, all the built-in parsers have the ability to parse squashed commits and separate them out into their own entry on the changelog. Therefore, the default template no longer needs to write out the full commit body. See the commit parser options if you want to enable/disable parsing squash commits. Resolves: #733 --- .../conventional/rst/.components/changes.rst.j2 | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/semantic_release/data/templates/conventional/rst/.components/changes.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/changes.rst.j2 index c6ef1cced..7498aa787 100644 --- a/src/semantic_release/data/templates/conventional/rst/.components/changes.rst.j2 +++ b/src/semantic_release/data/templates/conventional/rst/.components/changes.rst.j2 @@ -72,16 +72,8 @@ Additional Release Information %}{% set _ = post_paragraph_links.append(commit_hash_link_reference) %}{# # Generate the commit summary line and format it for RST - # Update the first line with reference links and if commit description - # has more than one line, add the rest of the lines - # NOTE: This is specifically to make sure to not hide contents - # of squash commits (until parse support is added) + # autoformatting the reference links #}{% set description = "* %s" | format(format_commit_summary_line(commit)) -%}{% if commit.descriptions | length > 1 -%}{% set description = "%s\n\n%s" | format( - description, commit.descriptions[1:] | join("\n\n") | trim - ) -%}{% endif %}{% set description = description | convert_md_to_rst %}{% set description = description | autofit_text_width(max_line_width, hanging_indent) %}{% set _ = commit_descriptions.append(description) From 615d8d22698cdfd0dfdffb5b65279c6b8bc09da3 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 15:37:22 -0600 Subject: [PATCH 034/120] refactor(parser-conventional): update parser code to separate from the deprecating angular parser --- .../commit_parser/conventional.py | 477 +++++++++++++++++- 1 file changed, 472 insertions(+), 5 deletions(-) diff --git a/src/semantic_release/commit_parser/conventional.py b/src/semantic_release/commit_parser/conventional.py index 9ee0b27fe..9a90c3ff4 100644 --- a/src/semantic_release/commit_parser/conventional.py +++ b/src/semantic_release/commit_parser/conventional.py @@ -1,19 +1,126 @@ from __future__ import annotations +import re +from functools import reduce +from itertools import zip_longest +from logging import getLogger +from re import compile as regexp +from textwrap import dedent +from typing import TYPE_CHECKING, Tuple + +from git.objects.commit import Commit from pydantic.dataclasses import dataclass -from semantic_release.commit_parser.angular import ( - AngularCommitParser, - AngularParserOptions, +from semantic_release.commit_parser._base import CommitParser, ParserOptions +from semantic_release.commit_parser.token import ( + ParsedCommit, + ParsedMessageResult, + ParseError, + ParseResult, +) +from semantic_release.commit_parser.util import ( + breaking_re, + deep_copy_commit, + force_str, + parse_paragraphs, ) +from semantic_release.enums import LevelBump +from semantic_release.errors import InvalidParserOptions +from semantic_release.helpers import sort_numerically, text_reducer + +if TYPE_CHECKING: # pragma: no cover + from git.objects.commit import Commit + + +def _logged_parse_error(commit: Commit, error: str) -> ParseError: + getLogger(__name__).debug(error) + return ParseError(commit, error=error) + + +# TODO: Remove from here, allow for user customization instead via options +# types with long names in changelog +LONG_TYPE_NAMES = { + "build": "build system", + "ci": "continuous integration", + "chore": "chores", + "docs": "documentation", + "feat": "features", + "fix": "bug fixes", + "perf": "performance improvements", + "refactor": "refactoring", + "style": "code style", + "test": "testing", +} @dataclass -class ConventionalCommitParserOptions(AngularParserOptions): +class ConventionalCommitParserOptions(ParserOptions): """Options dataclass for the ConventionalCommitParser.""" + minor_tags: Tuple[str, ...] = ("feat",) + """Commit-type prefixes that should result in a minor release bump.""" + + patch_tags: Tuple[str, ...] = ("fix", "perf") + """Commit-type prefixes that should result in a patch release bump.""" -class ConventionalCommitParser(AngularCommitParser): + other_allowed_tags: Tuple[str, ...] = ( + "build", + "chore", + "ci", + "docs", + "style", + "refactor", + "test", + ) + """Commit-type prefixes that are allowed but do not result in a version bump.""" + + allowed_tags: Tuple[str, ...] = ( + *minor_tags, + *patch_tags, + *other_allowed_tags, + ) + """ + All commit-type prefixes that are allowed. + + These are used to identify a valid commit message. If a commit message does not start with + one of these prefixes, it will not be considered a valid commit message. + """ + + default_bump_level: LevelBump = LevelBump.NO_RELEASE + """The minimum bump level to apply to valid commit message.""" + + # TODO: breaking change v10, change default to True + parse_squash_commits: bool = False + """Toggle flag for whether or not to parse squash commits""" + + # TODO: breaking change v10, change default to True + ignore_merge_commits: bool = False + """Toggle flag for whether or not to ignore merge commits""" + + @property + def tag_to_level(self) -> dict[str, LevelBump]: + """A mapping of commit tags to the level bump they should result in.""" + return self._tag_to_level + + def __post_init__(self) -> None: + self._tag_to_level: dict[str, LevelBump] = { + str(tag): level + for tag, level in [ + # we have to do a type ignore as zip_longest provides a type that is not specific enough + # for our expected output. Due to the empty second array, we know the first is always longest + # and that means no values in the first entry of the tuples will ever be a LevelBump. We + # apply a str() to make mypy happy although it will never happen. + *zip_longest(self.allowed_tags, (), fillvalue=self.default_bump_level), + *zip_longest(self.patch_tags, (), fillvalue=LevelBump.PATCH), + *zip_longest(self.minor_tags, (), fillvalue=LevelBump.MINOR), + ] + if "|" not in str(tag) + } + + +class ConventionalCommitParser( + CommitParser[ParseResult, ConventionalCommitParserOptions] +): """ A commit parser for projects conforming to the conventional commits specification. @@ -26,6 +133,366 @@ class ConventionalCommitParser(AngularCommitParser): def __init__(self, options: ConventionalCommitParserOptions | None = None) -> None: super().__init__(options) + try: + commit_type_pattern = regexp( + r"(?P%s)" % str.join("|", self.options.allowed_tags) + ) + except re.error as err: + raise InvalidParserOptions( + str.join( + "\n", + [ + f"Invalid options for {self.__class__.__name__}", + "Unable to create regular expression from configured commit-types.", + "Please check the configured commit-types and remove or escape any regular expression characters.", + ], + ) + ) from err + + self.commit_prefix = regexp( + str.join( + "", + [ + f"^{commit_type_pattern.pattern}", + r"(?:\((?P[^\n]+)\))?", + # TODO: remove ! support as it is not part of the angular commit spec (its part of conventional commits spec) + r"(?P!)?:\s+", + ], + ) + ) + + self.re_parser = regexp( + str.join( + "", + [ + self.commit_prefix.pattern, + r"(?P[^\n]+)", + r"(?:\n\n(?P.+))?", # commit body + ], + ), + flags=re.DOTALL, + ) + + # GitHub & Gitea use (#123), GitLab uses (!123), and BitBucket uses (pull request #123) + self.mr_selector = regexp( + r"[\t ]+\((?:pull request )?(?P[#!]\d+)\)[\t ]*$" + ) + self.issue_selector = regexp( + str.join( + "", + [ + r"^(?:clos(?:e|es|ed|ing)|fix(?:es|ed|ing)?|resolv(?:e|es|ed|ing)|implement(?:s|ed|ing)?):", + r"[\t ]+(?P.+)[\t ]*$", + ], + ), + flags=re.MULTILINE | re.IGNORECASE, + ) + self.notice_selector = regexp(r"^NOTICE: (?P.+)$") + self.filters = { + "typo-extra-spaces": (regexp(r"(\S) +(\S)"), r"\1 \2"), + "git-header-commit": ( + regexp(r"^[\t ]*commit [0-9a-f]+$\n?", flags=re.MULTILINE), + "", + ), + "git-header-author": ( + regexp(r"^[\t ]*Author: .+$\n?", flags=re.MULTILINE), + "", + ), + "git-header-date": ( + regexp(r"^[\t ]*Date: .+$\n?", flags=re.MULTILINE), + "", + ), + "git-squash-heading": ( + regexp( + r"^[\t ]*Squashed commit of the following:.*$\n?", + flags=re.MULTILINE, + ), + "", + ), + "git-squash-commit-prefix": ( + regexp( + str.join( + "", + [ + r"^(?:[\t ]*[*-][\t ]+|[\t ]+)?", # bullet points or indentation + commit_type_pattern.pattern + r"\b", # prior to commit type + ], + ), + flags=re.MULTILINE, + ), + # move commit type to the start of the line + r"\1", + ), + } + @staticmethod def get_default_options() -> ConventionalCommitParserOptions: return ConventionalCommitParserOptions() + + def commit_body_components_separator( + self, accumulator: dict[str, list[str]], text: str + ) -> dict[str, list[str]]: + if (match := breaking_re.match(text)) and (brk_desc := match.group(1)): + accumulator["breaking_descriptions"].append(brk_desc) + # TODO: breaking change v10, removes breaking change footers from descriptions + # return accumulator + + elif (match := self.notice_selector.match(text)) and ( + notice := match.group("notice") + ): + accumulator["notices"].append(notice) + # TODO: breaking change v10, removes notice footers from descriptions + # return accumulator + + elif match := self.issue_selector.search(text): + # if match := self.issue_selector.search(text): + predicate = regexp(r",? and | *[,;/& ] *").sub( + ",", match.group("issue_predicate") or "" + ) + # Almost all issue trackers use a number to reference an issue so + # we use a simple regexp to validate the existence of a number which helps filter out + # any non-issue references that don't fit our expected format + has_number = regexp(r"\d+") + new_issue_refs: set[str] = set( + filter( + lambda issue_str, validator=has_number: validator.search(issue_str), # type: ignore[arg-type] + predicate.split(","), + ) + ) + if new_issue_refs: + accumulator["linked_issues"] = sort_numerically( + set(accumulator["linked_issues"]).union(new_issue_refs) + ) + # TODO: breaking change v10, removes resolution footers from descriptions + # return accumulator + + # Prevent appending duplicate descriptions + if text not in accumulator["descriptions"]: + accumulator["descriptions"].append(text) + + return accumulator + + def parse_message(self, message: str) -> ParsedMessageResult | None: + if not (parsed := self.re_parser.match(message)): + return None + + parsed_break = parsed.group("break") + parsed_scope = parsed.group("scope") or "" + parsed_subject = parsed.group("subject") + parsed_text = parsed.group("text") + parsed_type = parsed.group("type") + + linked_merge_request = "" + if mr_match := self.mr_selector.search(parsed_subject): + linked_merge_request = mr_match.group("mr_number") + # TODO: breaking change v10, removes PR number from subject/descriptions + # expects changelog template to format the line accordingly + # parsed_subject = self.pr_selector.sub("", parsed_subject).strip() + + body_components: dict[str, list[str]] = reduce( + self.commit_body_components_separator, + [ + # Insert the subject before the other paragraphs + parsed_subject, + *parse_paragraphs(parsed_text or ""), + ], + { + "breaking_descriptions": [], + "descriptions": [], + "notices": [], + "linked_issues": [], + }, + ) + + level_bump = ( + LevelBump.MAJOR + if body_components["breaking_descriptions"] or parsed_break + else self.options.tag_to_level.get( + parsed_type, self.options.default_bump_level + ) + ) + + return ParsedMessageResult( + bump=level_bump, + type=parsed_type, + category=LONG_TYPE_NAMES.get(parsed_type, parsed_type), + scope=parsed_scope, + descriptions=tuple(body_components["descriptions"]), + breaking_descriptions=tuple(body_components["breaking_descriptions"]), + release_notices=tuple(body_components["notices"]), + linked_issues=tuple(body_components["linked_issues"]), + linked_merge_request=linked_merge_request, + ) + + @staticmethod + def is_merge_commit(commit: Commit) -> bool: + return len(commit.parents) > 1 + + def parse_commit(self, commit: Commit) -> ParseResult: + if not (parsed_msg_result := self.parse_message(force_str(commit.message))): + return _logged_parse_error( + commit, + f"Unable to parse commit message: {commit.message!r}", + ) + + return ParsedCommit.from_parsed_message_result(commit, parsed_msg_result) + + # Maybe this can be cached as an optimization, similar to how + # mypy/pytest use their own caching directories, for very large commit + # histories? + # The problem is the cache likely won't be present in CI environments + def parse(self, commit: Commit) -> ParseResult | list[ParseResult]: + """ + Parse a commit message + + If the commit message is a squashed merge commit, it will be split into + multiple commits, each of which will be parsed separately. Single commits + will be returned as a list of a single ParseResult. + """ + if self.options.ignore_merge_commits and self.is_merge_commit(commit): + return _logged_parse_error( + commit, "Ignoring merge commit: %s" % commit.hexsha[:8] + ) + + separate_commits: list[Commit] = ( + self.unsquash_commit(commit) + if self.options.parse_squash_commits + else [commit] + ) + + # Parse each commit individually if there were more than one + parsed_commits: list[ParseResult] = list( + map(self.parse_commit, separate_commits) + ) + + def add_linked_merge_request( + parsed_result: ParseResult, mr_number: str + ) -> ParseResult: + return ( + parsed_result + if not isinstance(parsed_result, ParsedCommit) + else ParsedCommit( + **{ + **parsed_result._asdict(), + "linked_merge_request": mr_number, + } + ) + ) + + # TODO: improve this for other VCS systems other than GitHub & BitBucket + # Github works as the first commit in a squash merge commit has the PR number + # appended to the first line of the commit message + lead_commit = next(iter(parsed_commits)) + + if isinstance(lead_commit, ParsedCommit) and lead_commit.linked_merge_request: + # If the first commit has linked merge requests, assume all commits + # are part of the same PR and add the linked merge requests to all + # parsed commits + parsed_commits = [ + lead_commit, + *map( + lambda parsed_result, mr=lead_commit.linked_merge_request: ( # type: ignore[misc] + add_linked_merge_request(parsed_result, mr) + ), + parsed_commits[1:], + ), + ] + + elif isinstance(lead_commit, ParseError) and ( + mr_match := self.mr_selector.search(force_str(lead_commit.message)) + ): + # Handle BitBucket Squash Merge Commits (see #1085), which have non angular commit + # format but include the PR number in the commit subject that we want to extract + linked_merge_request = mr_match.group("mr_number") + + # apply the linked MR to all commits + parsed_commits = [ + add_linked_merge_request(parsed_result, linked_merge_request) + for parsed_result in parsed_commits + ] + + return parsed_commits + + def unsquash_commit(self, commit: Commit) -> list[Commit]: + # GitHub EXAMPLE: + # feat(changelog): add autofit_text_width filter to template environment (#1062) + # + # This change adds an equivalent style formatter that can apply a text alignment + # to a maximum width and also maintain an indent over paragraphs of text + # + # * docs(changelog-templates): add definition & usage of autofit_text_width template filter + # + # * test(changelog-context): add test cases to check autofit_text_width filter use + # + # `git merge --squash` EXAMPLE: + # Squashed commit of the following: + # + # commit 63ec09b9e844e616dcaa7bae35a0b66671b59fbb + # Author: codejedi365 + # Date: Sun Oct 13 12:05:23 2024 -0600 + # + # feat(release-config): some commit subject + # + + # Return a list of artificial commits (each with a single commit message) + return [ + # create a artificial commit object (copy of original but with modified message) + Commit( + **{ + **deep_copy_commit(commit), + "message": commit_msg, + } + ) + for commit_msg in self.unsquash_commit_message(force_str(commit.message)) + ] or [commit] + + def unsquash_commit_message(self, message: str) -> list[str]: + normalized_message = message.replace("\r", "").strip() + + # split by obvious separate commits (applies to manual git squash merges) + obvious_squashed_commits = self.filters["git-header-commit"][0].split( + normalized_message + ) + + separate_commit_msgs: list[str] = reduce( + lambda all_msgs, msgs: all_msgs + msgs, + map(self._find_squashed_commits_in_str, obvious_squashed_commits), + [], + ) + + return list(filter(None, separate_commit_msgs)) + + def _find_squashed_commits_in_str(self, message: str) -> list[str]: + separate_commit_msgs: list[str] = [] + current_msg = "" + + for paragraph in filter(None, message.strip().split("\n\n")): + # Apply filters to normalize the paragraph + clean_paragraph = reduce(text_reducer, self.filters.values(), paragraph) + + # remove any filtered (and now empty) paragraphs (ie. the git headers) + if not clean_paragraph.strip(): + continue + + # Check if the paragraph is the start of a new angular commit + if not self.commit_prefix.search(clean_paragraph): + if not separate_commit_msgs and not current_msg: + # if there are no separate commit messages and no current message + # then this is the first commit message + current_msg = dedent(clean_paragraph) + continue + + # append the paragraph as part of the previous commit message + if current_msg: + current_msg += f"\n\n{dedent(clean_paragraph)}" + # else: drop the paragraph + continue + + # Since we found the start of the new commit, store any previous commit + # message separately and start the new commit message + if current_msg: + separate_commit_msgs.append(current_msg) + + current_msg = clean_paragraph + + return [*separate_commit_msgs, current_msg] From f703a757abc5b343f61e9a4cc09bbd6a92b7b7f4 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 15:58:11 -0600 Subject: [PATCH 035/120] refactor(parser-scipy): update parser code to separate from the deprecating angular parser --- src/semantic_release/commit_parser/scipy.py | 429 ++++++++++++++++++-- 1 file changed, 404 insertions(+), 25 deletions(-) diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index 6234cfdf3..1e014b6f8 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -46,29 +46,39 @@ from __future__ import annotations -import logging +import re +from functools import reduce +from itertools import zip_longest +from logging import getLogger +from re import compile as regexp +from textwrap import dedent from typing import TYPE_CHECKING, Tuple +from git.objects.commit import Commit from pydantic.dataclasses import dataclass -from semantic_release.commit_parser.angular import ( - AngularCommitParser, - AngularParserOptions, -) +from semantic_release.commit_parser._base import CommitParser, ParserOptions from semantic_release.commit_parser.token import ( + ParsedCommit, ParsedMessageResult, ParseError, + ParseResult, +) +from semantic_release.commit_parser.util import ( + deep_copy_commit, + force_str, + parse_paragraphs, ) from semantic_release.enums import LevelBump +from semantic_release.errors import InvalidParserOptions +from semantic_release.helpers import sort_numerically, text_reducer if TYPE_CHECKING: # pragma: no cover from git.objects.commit import Commit -logger = logging.getLogger(__name__) - def _logged_parse_error(commit: Commit, error: str) -> ParseError: - logger.debug(error) + getLogger(__name__).debug(error) return ParseError(commit, error=error) @@ -93,7 +103,7 @@ def _logged_parse_error(commit: Commit, error: str) -> ParseError: @dataclass -class ScipyParserOptions(AngularParserOptions): +class ScipyParserOptions(ParserOptions): """ Options dataclass for ScipyCommitParser @@ -110,10 +120,7 @@ class ScipyParserOptions(AngularParserOptions): patch_tags: Tuple[str, ...] = ("BLD", "BUG", "MAINT") """Commit-type prefixes that should result in a patch release bump.""" - allowed_tags: Tuple[str, ...] = ( - *major_tags, - *minor_tags, - *patch_tags, + other_allowed_tags: Tuple[str, ...] = ( "BENCH", "DOC", "STY", @@ -121,6 +128,14 @@ class ScipyParserOptions(AngularParserOptions): "REL", "TEST", ) + """Commit-type prefixes that are allowed but do not result in a version bump.""" + + allowed_tags: Tuple[str, ...] = ( + *major_tags, + *minor_tags, + *patch_tags, + *other_allowed_tags, + ) """ All commit-type prefixes that are allowed. @@ -132,15 +147,39 @@ class ScipyParserOptions(AngularParserOptions): default_level_bump: LevelBump = LevelBump.NO_RELEASE """The minimum bump level to apply to valid commit message.""" + # TODO: breaking change v10, change default to True + parse_squash_commits: bool = False + """Toggle flag for whether or not to parse squash commits""" + + # TODO: breaking change v10, change default to True + ignore_merge_commits: bool = False + """Toggle flag for whether or not to ignore merge commits""" + + @property + def tag_to_level(self) -> dict[str, LevelBump]: + """A mapping of commit tags to the level bump they should result in.""" + return self._tag_to_level + def __post_init__(self) -> None: # TODO: breaking v10, remove as the name is now consistent self.default_bump_level = self.default_level_bump - super().__post_init__() - for tag in self.major_tags: - self._tag_to_level[tag] = LevelBump.MAJOR - - -class ScipyCommitParser(AngularCommitParser): + self._tag_to_level: dict[str, LevelBump] = { + str(tag): level + for tag, level in [ + # we have to do a type ignore as zip_longest provides a type that is not specific enough + # for our expected output. Due to the empty second array, we know the first is always longest + # and that means no values in the first entry of the tuples will ever be a LevelBump. We + # apply a str() to make mypy happy although it will never happen. + *zip_longest(self.allowed_tags, (), fillvalue=self.default_bump_level), + *zip_longest(self.patch_tags, (), fillvalue=LevelBump.PATCH), + *zip_longest(self.minor_tags, (), fillvalue=LevelBump.MINOR), + *zip_longest(self.major_tags, (), fillvalue=LevelBump.MAJOR), + ] + if "|" not in str(tag) + } + + +class ScipyCommitParser(CommitParser[ParseResult, ScipyParserOptions]): """Parser for scipy-style commit messages""" # TODO: Deprecate in lieu of get_default_options() @@ -149,18 +188,358 @@ class ScipyCommitParser(AngularCommitParser): def __init__(self, options: ScipyParserOptions | None = None) -> None: super().__init__(options) + try: + commit_type_pattern = regexp( + r"(?P%s)" % str.join("|", self.options.allowed_tags) + ) + except re.error as err: + raise InvalidParserOptions( + str.join( + "\n", + [ + f"Invalid options for {self.__class__.__name__}", + "Unable to create regular expression from configured commit-types.", + "Please check the configured commit-types and remove or escape any regular expression characters.", + ], + ) + ) from err + + self.commit_prefix = regexp( + str.join( + "", + [ + f"^{commit_type_pattern.pattern}", + r"(?:\((?P[^\n]+)\))?", + r":\s+", + ], + ) + ) + + self.re_parser = regexp( + str.join( + "", + [ + self.commit_prefix.pattern, + r"(?P[^\n]+)", + r"(?:\n\n(?P.+))?", # commit body + ], + ), + flags=re.DOTALL, + ) + + # GitHub & Gitea use (#123), GitLab uses (!123), and BitBucket uses (pull request #123) + self.mr_selector = regexp( + r"[\t ]+\((?:pull request )?(?P[#!]\d+)\)[\t ]*$" + ) + self.issue_selector = regexp( + str.join( + "", + [ + r"^(?:clos(?:e|es|ed|ing)|fix(?:es|ed|ing)?|resolv(?:e|es|ed|ing)|implement(?:s|ed|ing)?):", + r"[\t ]+(?P.+)[\t ]*$", + ], + ), + flags=re.MULTILINE | re.IGNORECASE, + ) + self.notice_selector = regexp(r"^NOTICE: (?P.+)$") + self.filters = { + "typo-extra-spaces": (regexp(r"(\S) +(\S)"), r"\1 \2"), + "git-header-commit": ( + regexp(r"^[\t ]*commit [0-9a-f]+$\n?", flags=re.MULTILINE), + "", + ), + "git-header-author": ( + regexp(r"^[\t ]*Author: .+$\n?", flags=re.MULTILINE), + "", + ), + "git-header-date": ( + regexp(r"^[\t ]*Date: .+$\n?", flags=re.MULTILINE), + "", + ), + "git-squash-heading": ( + regexp( + r"^[\t ]*Squashed commit of the following:.*$\n?", + flags=re.MULTILINE, + ), + "", + ), + "git-squash-commit-prefix": ( + regexp( + str.join( + "", + [ + r"^(?:[\t ]*[*-][\t ]+|[\t ]+)?", # bullet points or indentation + commit_type_pattern.pattern + r"\b", # prior to commit type + ], + ), + flags=re.MULTILINE, + ), + # move commit type to the start of the line + r"\1", + ), + } + @staticmethod def get_default_options() -> ScipyParserOptions: return ScipyParserOptions() + def commit_body_components_separator( + self, accumulator: dict[str, list[str]], text: str + ) -> dict[str, list[str]]: + if (match := self.notice_selector.match(text)) and ( + notice := match.group("notice") + ): + accumulator["notices"].append(notice) + # TODO: breaking change v10, removes notice footers from descriptions + # return accumulator + + elif match := self.issue_selector.search(text): + # if match := self.issue_selector.search(text): + predicate = regexp(r",? and | *[,;/& ] *").sub( + ",", match.group("issue_predicate") or "" + ) + # Almost all issue trackers use a number to reference an issue so + # we use a simple regexp to validate the existence of a number which helps filter out + # any non-issue references that don't fit our expected format + has_number = regexp(r"\d+") + new_issue_refs: set[str] = set( + filter( + lambda issue_str, validator=has_number: validator.search(issue_str), # type: ignore[arg-type] + predicate.split(","), + ) + ) + if new_issue_refs: + accumulator["linked_issues"] = sort_numerically( + set(accumulator["linked_issues"]).union(new_issue_refs) + ) + # TODO: breaking change v10, removes resolution footers from descriptions + # return accumulator + + # Prevent appending duplicate descriptions + if text not in accumulator["descriptions"]: + accumulator["descriptions"].append(text) + + return accumulator + def parse_message(self, message: str) -> ParsedMessageResult | None: - return ( - None - if not (pmsg_result := super().parse_message(message)) - else ParsedMessageResult( + if not (parsed := self.re_parser.match(message)): + return None + + parsed_scope = parsed.group("scope") or "" + parsed_subject = parsed.group("subject") + parsed_text = parsed.group("text") + parsed_type = parsed.group("type") + + linked_merge_request = "" + if mr_match := self.mr_selector.search(parsed_subject): + linked_merge_request = mr_match.group("mr_number") + # TODO: breaking change v10, removes PR number from subject/descriptions + # expects changelog template to format the line accordingly + # parsed_subject = self.pr_selector.sub("", parsed_subject).strip() + + body_components: dict[str, list[str]] = reduce( + self.commit_body_components_separator, + [ + # Insert the subject before the other paragraphs + parsed_subject, + *parse_paragraphs(parsed_text or ""), + ], + { + "descriptions": [], + "notices": [], + "linked_issues": [], + }, + ) + + level_bump = self.options.tag_to_level.get( + parsed_type, self.options.default_bump_level + ) + + return ParsedMessageResult( + bump=level_bump, + type=parsed_type, + category=tag_to_section.get(parsed_type, "None"), + scope=parsed_scope, + descriptions=tuple( + body_components["descriptions"] + if level_bump != LevelBump.MAJOR + else [parsed_subject] + ), + breaking_descriptions=tuple( + body_components["descriptions"][1:] + if level_bump == LevelBump.MAJOR + else [] + ), + release_notices=tuple(body_components["notices"]), + linked_issues=tuple(body_components["linked_issues"]), + linked_merge_request=linked_merge_request, + ) + + @staticmethod + def is_merge_commit(commit: Commit) -> bool: + return len(commit.parents) > 1 + + def parse_commit(self, commit: Commit) -> ParseResult: + if not (parsed_msg_result := self.parse_message(force_str(commit.message))): + return _logged_parse_error( + commit, + f"Unable to parse commit message: {commit.message!r}", + ) + + return ParsedCommit.from_parsed_message_result(commit, parsed_msg_result) + + def parse(self, commit: Commit) -> ParseResult | list[ParseResult]: + """ + Parse a commit message + + If the commit message is a squashed merge commit, it will be split into + multiple commits, each of which will be parsed separately. Single commits + will be returned as a list of a single ParseResult. + """ + if self.options.ignore_merge_commits and self.is_merge_commit(commit): + return _logged_parse_error( + commit, "Ignoring merge commit: %s" % commit.hexsha[:8] + ) + + separate_commits: list[Commit] = ( + self.unsquash_commit(commit) + if self.options.parse_squash_commits + else [commit] + ) + + # Parse each commit individually if there were more than one + parsed_commits: list[ParseResult] = list( + map(self.parse_commit, separate_commits) + ) + + def add_linked_merge_request( + parsed_result: ParseResult, mr_number: str + ) -> ParseResult: + return ( + parsed_result + if not isinstance(parsed_result, ParsedCommit) + else ParsedCommit( + **{ + **parsed_result._asdict(), + "linked_merge_request": mr_number, + } + ) + ) + + # TODO: improve this for other VCS systems other than GitHub & BitBucket + # Github works as the first commit in a squash merge commit has the PR number + # appended to the first line of the commit message + lead_commit = next(iter(parsed_commits)) + + if isinstance(lead_commit, ParsedCommit) and lead_commit.linked_merge_request: + # If the first commit has linked merge requests, assume all commits + # are part of the same PR and add the linked merge requests to all + # parsed commits + parsed_commits = [ + lead_commit, + *map( + lambda parsed_result, mr=lead_commit.linked_merge_request: ( # type: ignore[misc] + add_linked_merge_request(parsed_result, mr) + ), + parsed_commits[1:], + ), + ] + + elif isinstance(lead_commit, ParseError) and ( + mr_match := self.mr_selector.search(force_str(lead_commit.message)) + ): + # Handle BitBucket Squash Merge Commits (see #1085), which have non angular commit + # format but include the PR number in the commit subject that we want to extract + linked_merge_request = mr_match.group("mr_number") + + # apply the linked MR to all commits + parsed_commits = [ + add_linked_merge_request(parsed_result, linked_merge_request) + for parsed_result in parsed_commits + ] + + return parsed_commits + + def unsquash_commit(self, commit: Commit) -> list[Commit]: + # GitHub EXAMPLE: + # feat(changelog): add autofit_text_width filter to template environment (#1062) + # + # This change adds an equivalent style formatter that can apply a text alignment + # to a maximum width and also maintain an indent over paragraphs of text + # + # * docs(changelog-templates): add definition & usage of autofit_text_width template filter + # + # * test(changelog-context): add test cases to check autofit_text_width filter use + # + # `git merge --squash` EXAMPLE: + # Squashed commit of the following: + # + # commit 63ec09b9e844e616dcaa7bae35a0b66671b59fbb + # Author: codejedi365 + # Date: Sun Oct 13 12:05:23 2024 -0600 + # + # feat(release-config): some commit subject + # + + # Return a list of artificial commits (each with a single commit message) + return [ + # create a artificial commit object (copy of original but with modified message) + Commit( **{ - **pmsg_result._asdict(), - "category": tag_to_section.get(pmsg_result.type, "None"), + **deep_copy_commit(commit), + "message": commit_msg, } ) + for commit_msg in self.unsquash_commit_message(force_str(commit.message)) + ] or [commit] + + def unsquash_commit_message(self, message: str) -> list[str]: + normalized_message = message.replace("\r", "").strip() + + # split by obvious separate commits (applies to manual git squash merges) + obvious_squashed_commits = self.filters["git-header-commit"][0].split( + normalized_message + ) + + separate_commit_msgs: list[str] = reduce( + lambda all_msgs, msgs: all_msgs + msgs, + map(self._find_squashed_commits_in_str, obvious_squashed_commits), + [], ) + + return list(filter(None, separate_commit_msgs)) + + def _find_squashed_commits_in_str(self, message: str) -> list[str]: + separate_commit_msgs: list[str] = [] + current_msg = "" + + for paragraph in filter(None, message.strip().split("\n\n")): + # Apply filters to normalize the paragraph + clean_paragraph = reduce(text_reducer, self.filters.values(), paragraph) + + # remove any filtered (and now empty) paragraphs (ie. the git headers) + if not clean_paragraph.strip(): + continue + + # Check if the paragraph is the start of a new angular commit + if not self.commit_prefix.search(clean_paragraph): + if not separate_commit_msgs and not current_msg: + # if there are no separate commit messages and no current message + # then this is the first commit message + current_msg = dedent(clean_paragraph) + continue + + # append the paragraph as part of the previous commit message + if current_msg: + current_msg += f"\n\n{dedent(clean_paragraph)}" + # else: drop the paragraph + continue + + # Since we found the start of the new commit, store any previous commit + # message separately and start the new commit message + if current_msg: + separate_commit_msgs.append(current_msg) + + current_msg = clean_paragraph + + return [*separate_commit_msgs, current_msg] From b1bb0e55910715754eebef6cb5b21ebed5ee8d68 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 23 Oct 2024 23:51:41 -0600 Subject: [PATCH 036/120] fix(parser-conventional)!: remove issue footer messages from commit descriptions BREAKING CHANGE: Any issue resolution footers that the parser detects will now be removed from the `commit.descriptions[]` list. Previously, the descriptions included all text from the commit message but now that the parser pulls out the issue numbers the numbers will be included in the `commit.linked_issues` tuple for user extraction in any changelog generation. --- src/semantic_release/commit_parser/conventional.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/commit_parser/conventional.py b/src/semantic_release/commit_parser/conventional.py index 9a90c3ff4..25b60642a 100644 --- a/src/semantic_release/commit_parser/conventional.py +++ b/src/semantic_release/commit_parser/conventional.py @@ -263,8 +263,7 @@ def commit_body_components_separator( accumulator["linked_issues"] = sort_numerically( set(accumulator["linked_issues"]).union(new_issue_refs) ) - # TODO: breaking change v10, removes resolution footers from descriptions - # return accumulator + return accumulator # Prevent appending duplicate descriptions if text not in accumulator["descriptions"]: From 3cfee76032662bda6fbdd7e2585193213e4f9da2 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 16:03:23 -0600 Subject: [PATCH 037/120] fix(parser-scipy)!: remove issue footer messages from commit descriptions BREAKING CHANGE: Any issue resolution footers that the parser detects will now be removed from the commit.descriptions[] list. Previously, the descriptions included all text from the commit message but now that the parser pulls out the issue numbers the numbers will be included in the commit.linked_issues tuple for user extraction in any changelog generation. --- src/semantic_release/commit_parser/scipy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index 1e014b6f8..a21223944 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -312,8 +312,7 @@ def commit_body_components_separator( accumulator["linked_issues"] = sort_numerically( set(accumulator["linked_issues"]).union(new_issue_refs) ) - # TODO: breaking change v10, removes resolution footers from descriptions - # return accumulator + return accumulator # Prevent appending duplicate descriptions if text not in accumulator["descriptions"]: From b757603e77ebe26d8a14758d78fd21163a9059b2 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 17:04:24 -0600 Subject: [PATCH 038/120] fix(parser-emoji)!: remove issue footer messages from commit descriptions BREAKING CHANGE: Any issue resolution footers that the parser detects will now be removed from the `commit.descriptions[]` list. Previously, the descriptions included all text from the commit message but now that the parser pulls out the issue numbers the numbers will be included in the `commit.linked_issues` tuple for user extraction in any changelog generation. --- src/semantic_release/commit_parser/emoji.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/commit_parser/emoji.py b/src/semantic_release/commit_parser/emoji.py index 5be4f1ec3..d5ec89dd1 100644 --- a/src/semantic_release/commit_parser/emoji.py +++ b/src/semantic_release/commit_parser/emoji.py @@ -262,8 +262,7 @@ def commit_body_components_separator( accumulator["linked_issues"] = sort_numerically( set(accumulator["linked_issues"]).union(new_issue_refs) ) - # TODO: breaking change v10, removes resolution footers from descriptions - # return accumulator + return accumulator # Prevent appending duplicate descriptions if text not in accumulator["descriptions"]: From b271cbb2d3e8b86d07d1358b2e7424ccff6ae186 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 23 Oct 2024 23:52:50 -0600 Subject: [PATCH 039/120] fix(parser-conventional)!: remove breaking change footer messages from commit descriptions BREAKING CHANGE: Any breaking change footer messages that the conventional commit parser detects will now be removed from the `commit.descriptions[]` list but maintained in and only in the `commit.breaking_descriptions[]` list. Previously, the descriptions included all text from the commit message but that was redundant as the default changelog now handles breaking change footers in its own section. --- src/semantic_release/commit_parser/conventional.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/semantic_release/commit_parser/conventional.py b/src/semantic_release/commit_parser/conventional.py index 25b60642a..a7a3fadff 100644 --- a/src/semantic_release/commit_parser/conventional.py +++ b/src/semantic_release/commit_parser/conventional.py @@ -234,10 +234,9 @@ def commit_body_components_separator( ) -> dict[str, list[str]]: if (match := breaking_re.match(text)) and (brk_desc := match.group(1)): accumulator["breaking_descriptions"].append(brk_desc) - # TODO: breaking change v10, removes breaking change footers from descriptions - # return accumulator + return accumulator - elif (match := self.notice_selector.match(text)) and ( + if (match := self.notice_selector.match(text)) and ( notice := match.group("notice") ): accumulator["notices"].append(notice) From 7e8dc13c0b048a95d01f7aecfbe4eeedcddec9a4 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 17:45:27 -0600 Subject: [PATCH 040/120] fix(parser-conventional)!: remove release notice footer messages from commit descriptions BREAKING CHANGE: Any release notice footer messages that the commit parser detects will now be removed from the `commit.descriptions[]` list but maintained in and only in the `commit.notices[]` list. Previously, the descriptions included all text from the commit message but that was redundant as the default changelog now handles release notice footers in its own section. --- src/semantic_release/commit_parser/conventional.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/semantic_release/commit_parser/conventional.py b/src/semantic_release/commit_parser/conventional.py index a7a3fadff..3e1a73cf3 100644 --- a/src/semantic_release/commit_parser/conventional.py +++ b/src/semantic_release/commit_parser/conventional.py @@ -240,10 +240,9 @@ def commit_body_components_separator( notice := match.group("notice") ): accumulator["notices"].append(notice) - # TODO: breaking change v10, removes notice footers from descriptions - # return accumulator + return accumulator - elif match := self.issue_selector.search(text): + if match := self.issue_selector.search(text): # if match := self.issue_selector.search(text): predicate = regexp(r",? and | *[,;/& ] *").sub( ",", match.group("issue_predicate") or "" From 58308e31bb6306aac3a985af01eb779dc923d3f0 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 23 Oct 2024 23:52:50 -0600 Subject: [PATCH 041/120] fix(parser-scipy)!: remove release notice footer messages from commit descriptions BREAKING CHANGE: Any release notice footer messages that the commit parser detects will now be removed from the `commit.descriptions[]` list but maintained in and only in the `commit.notices[]` list. Previously, the descriptions included all text from the commit message but that was redundant as the default changelog now handles release notice footers in its own section. --- src/semantic_release/commit_parser/scipy.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index a21223944..a6484fe20 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -290,10 +290,9 @@ def commit_body_components_separator( notice := match.group("notice") ): accumulator["notices"].append(notice) - # TODO: breaking change v10, removes notice footers from descriptions - # return accumulator + return accumulator - elif match := self.issue_selector.search(text): + if match := self.issue_selector.search(text): # if match := self.issue_selector.search(text): predicate = regexp(r",? and | *[,;/& ] *").sub( ",", match.group("issue_predicate") or "" From b6307cb649043bbcc7ad9f15ac5ac6728914f443 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 17:07:07 -0600 Subject: [PATCH 042/120] fix(parser-emoji)!: remove release notice footer messages from commit descriptions BREAKING CHANGE: Any release notice footer messages that the emoji commit parser detects will now be removed from the `commit.descriptions[]` list but maintained in and only in the `commit.notices[]` list. Previously, the descriptions included all text from the commit message but that was redundant as the default changelog now handles release notice footers in its own section. --- src/semantic_release/commit_parser/emoji.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/semantic_release/commit_parser/emoji.py b/src/semantic_release/commit_parser/emoji.py index d5ec89dd1..2333f8273 100644 --- a/src/semantic_release/commit_parser/emoji.py +++ b/src/semantic_release/commit_parser/emoji.py @@ -239,10 +239,9 @@ def commit_body_components_separator( notice := match.group("notice") ): accumulator["notices"].append(notice) - # TODO: breaking change v10, removes notice footers from descriptions - # return accumulator + return accumulator - elif self.options.parse_linked_issues and ( + if self.options.parse_linked_issues and ( match := self.issue_selector.search(text) ): predicate = regexp(r",? and | *[,;/& ] *").sub( From eed63fa9f6e762f55700fc85ef3ebdc0d3144f21 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 23 Oct 2024 23:56:21 -0600 Subject: [PATCH 043/120] fix(parser-conventional)!: remove PR/MR references from commit subject line BREAKING CHANGE: Generally, a pull request or merge request number reference is included in the subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks for this reference and extracts it into the `commit.linked_merge_request` and the `commit.linked_pull_request` attributes of a commit object. Since this is now pulled out individually, it is cleaner to remove this from the first line of the `commit.descriptions` list (ie. the subject line) so that changelog macros do not have to replace the text but instead only append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator (`#` or `!`). --- src/semantic_release/commit_parser/conventional.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/semantic_release/commit_parser/conventional.py b/src/semantic_release/commit_parser/conventional.py index 3e1a73cf3..067caf3c7 100644 --- a/src/semantic_release/commit_parser/conventional.py +++ b/src/semantic_release/commit_parser/conventional.py @@ -282,9 +282,7 @@ def parse_message(self, message: str) -> ParsedMessageResult | None: linked_merge_request = "" if mr_match := self.mr_selector.search(parsed_subject): linked_merge_request = mr_match.group("mr_number") - # TODO: breaking change v10, removes PR number from subject/descriptions - # expects changelog template to format the line accordingly - # parsed_subject = self.pr_selector.sub("", parsed_subject).strip() + parsed_subject = self.mr_selector.sub("", parsed_subject).strip() body_components: dict[str, list[str]] = reduce( self.commit_body_components_separator, From da4140f3e3a2ed03c05064f35561b4584f517105 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 23 Oct 2024 23:56:21 -0600 Subject: [PATCH 044/120] fix(parser-scipy)!: remove PR/MR references from commit subject line BREAKING CHANGE: Generally, a pull request or merge request number reference is included in the subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks for this reference and extracts it into the `commit.linked_merge_request` and the `commit.linked_pull_request` attributes of a commit object. Since this is now pulled out individually, it is cleaner to remove this from the first line of the `commit.descriptions` list (ie. the subject line) so that changelog macros do not have to replace the text but instead only append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator (`#` or `!`). --- src/semantic_release/commit_parser/scipy.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index a6484fe20..e19725a0b 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -331,9 +331,7 @@ def parse_message(self, message: str) -> ParsedMessageResult | None: linked_merge_request = "" if mr_match := self.mr_selector.search(parsed_subject): linked_merge_request = mr_match.group("mr_number") - # TODO: breaking change v10, removes PR number from subject/descriptions - # expects changelog template to format the line accordingly - # parsed_subject = self.pr_selector.sub("", parsed_subject).strip() + parsed_subject = self.mr_selector.sub("", parsed_subject).strip() body_components: dict[str, list[str]] = reduce( self.commit_body_components_separator, From 16465f133386b09627d311727a6f8d24dd8f174f Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 17:12:36 -0600 Subject: [PATCH 045/120] fix(parser-emoji)!: remove PR/MR references from commit subject line BREAKING CHANGE: Generally, a pull request or merge request number reference is included in the subject line at the end within parentheses on some common VCS's (e.g. GitHub). PSR now looks for these references and extract it into the `commit.linked_merge_request` field of a commit object. Since this is now pulled out individually, it is cleaner to remove this from the first line of the `commit.descriptions` list (ie. the subject line) so that changelog macros do not have to replace the text but instead only append a PR/MR link to the end of the line. The reference will maintain the PR/MR prefix indicator (e.g. `#` or `!`). --- src/semantic_release/commit_parser/emoji.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/semantic_release/commit_parser/emoji.py b/src/semantic_release/commit_parser/emoji.py index 2333f8273..dabef9e5f 100644 --- a/src/semantic_release/commit_parser/emoji.py +++ b/src/semantic_release/commit_parser/emoji.py @@ -275,9 +275,7 @@ def parse_message(self, message: str) -> ParsedMessageResult: linked_merge_request = "" if mr_match := self.mr_selector.search(subject): linked_merge_request = mr_match.group("mr_number") - # TODO: breaking change v10, removes PR number from subject/descriptions - # expects changelog template to format the line accordingly - # subject = self.mr_selector.sub("", subject).strip() + subject = self.mr_selector.sub("", subject).strip() # Search for emoji of the highest importance in the subject match = self.emoji_selector.search(subject) From 6fcdc99e9462b1186ea9488fc14e4e18f8c7fdb3 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 11 Nov 2024 14:39:31 -0700 Subject: [PATCH 046/120] feat(parser-conventional)!: set parser to evaluate all squashed commits by default BREAKING CHANGE: The configuration setting `commit_parser_options.parse_squash_commits` is now set to `true` by default. The feature to parse squash commits was introduced in `v9.17.0` and was originally set to `false` to prevent unexpected results on a non-breaking update. The parse squash commits feature attempts to find additional commits of the same commit type within the body of a single commit message. When squash commits are found, Python Semantic Release will separate out each commit into its own artificial commit object and parse them individually. This potentially can change the resulting version bump if a larger bump was detected within the squashed components. It also allows for the changelog and release notes to separately order and display each commit as originally written. If this is not desired, you will need to update your configuration to change the new setting to `false`. --- src/semantic_release/commit_parser/conventional.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/commit_parser/conventional.py b/src/semantic_release/commit_parser/conventional.py index 067caf3c7..267a505d3 100644 --- a/src/semantic_release/commit_parser/conventional.py +++ b/src/semantic_release/commit_parser/conventional.py @@ -89,8 +89,7 @@ class ConventionalCommitParserOptions(ParserOptions): default_bump_level: LevelBump = LevelBump.NO_RELEASE """The minimum bump level to apply to valid commit message.""" - # TODO: breaking change v10, change default to True - parse_squash_commits: bool = False + parse_squash_commits: bool = True """Toggle flag for whether or not to parse squash commits""" # TODO: breaking change v10, change default to True From 634fffea29157e9b6305b21802c78ac245454265 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 11 Nov 2024 14:39:31 -0700 Subject: [PATCH 047/120] feat(parser-scipy)!: set parser to evaluate all squashed commits by default BREAKING CHANGE: The configuration setting `commit_parser_options.parse_squash_commits` is now set to `true` by default. The feature to parse squash commits was introduced in `v9.17.0` and was originally set to `false` to prevent unexpected results on a non-breaking update. The parse squash commits feature attempts to find additional commits of the same commit type within the body of a single commit message. When squash commits are found, Python Semantic Release will separate out each commit into its own artificial commit object and parse them individually. This potentially can change the resulting version bump if a larger bump was detected within the squashed components. It also allows for the changelog and release notes to separately order and display each commit as originally written. If this is not desired, you will need to update your configuration to change the new setting to `false`. --- src/semantic_release/commit_parser/scipy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index e19725a0b..3615fff94 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -147,8 +147,7 @@ class ScipyParserOptions(ParserOptions): default_level_bump: LevelBump = LevelBump.NO_RELEASE """The minimum bump level to apply to valid commit message.""" - # TODO: breaking change v10, change default to True - parse_squash_commits: bool = False + parse_squash_commits: bool = True """Toggle flag for whether or not to parse squash commits""" # TODO: breaking change v10, change default to True From 514a922fa87721e2500062dcae841bedd84dc1fe Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 17:17:17 -0600 Subject: [PATCH 048/120] feat(parser-emoji)!: set parser to evaluate all squashed commits by default BREAKING CHANGE: The configuration setting `commit_parser_options.parse_squash_commits` is now set to `true` by default. The feature to parse squash commits was introduced in `v9.17.0` and was originally set to `false` to prevent unexpected results on a non-breaking update. The parse squash commits feature attempts to find additional commits of the same commit type within the body of a single commit message. When squash commits are found, Python Semantic Release will separate out each commit into its own artificial commit object and parse them individually. This potentially can change the resulting version bump if a larger bump was detected within the squashed components. It also allows for the changelog and release notes to separately order and display each commit as originally written. If this is not desired, you will need to update your configuration to change the new setting to `false`. --- src/semantic_release/commit_parser/emoji.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/commit_parser/emoji.py b/src/semantic_release/commit_parser/emoji.py index dabef9e5f..6fe965b02 100644 --- a/src/semantic_release/commit_parser/emoji.py +++ b/src/semantic_release/commit_parser/emoji.py @@ -94,8 +94,7 @@ class EmojiParserOptions(ParserOptions): a whitespace separator. """ - # TODO: breaking change v10, change default to True - parse_squash_commits: bool = False + parse_squash_commits: bool = True """Toggle flag for whether or not to parse squash commits""" # TODO: breaking change v10, change default to True From 59bf08440a15269afaac81d78dd03ee418f9fd6b Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 11 Nov 2024 14:41:22 -0700 Subject: [PATCH 049/120] feat(parser-conventional)!: set parser to ignore merge commits by default BREAKING CHANGE: The configuration setting `commit_parser_options.ignore_merge_commits` is now set to `true` by default. The feature to ignore squash commits was introduced in `v9.18.0` and was originally set to `false` to prevent unexpected results on a non-breaking update. The ignore merge commits feature prevents additional unnecessary processing on a commit message that likely will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should they). The larger issue with merge commits is that they ultimately are a full copy of all the changes that were previously created and committed. The merge commit itself ensures that the previous commit tree is maintained in history, therefore the commit message always exists. If merge commits are parsed, it generally creates duplicate messages that will end up in your changelog, which is less than desired in most cases. If you have previously used the `changelog.exclude_commit_patterns` functionality to ignore merge commit messages then you will want this setting set to `true` to improve parsing speed. You can also now remove the merge commit exclude pattern from the list as well to improve parsing speed. If this functionality is not desired, you will need to update your configuration to change the new setting to `false`. --- src/semantic_release/commit_parser/conventional.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/commit_parser/conventional.py b/src/semantic_release/commit_parser/conventional.py index 267a505d3..25c0ae207 100644 --- a/src/semantic_release/commit_parser/conventional.py +++ b/src/semantic_release/commit_parser/conventional.py @@ -92,8 +92,7 @@ class ConventionalCommitParserOptions(ParserOptions): parse_squash_commits: bool = True """Toggle flag for whether or not to parse squash commits""" - # TODO: breaking change v10, change default to True - ignore_merge_commits: bool = False + ignore_merge_commits: bool = True """Toggle flag for whether or not to ignore merge commits""" @property From d4f128e75e33256c0163fbb475c7c41e18f65147 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 11 Nov 2024 14:41:22 -0700 Subject: [PATCH 050/120] feat(parser-scipy)!: set parser to ignore merge commits by default BREAKING CHANGE: The configuration setting `commit_parser_options.ignore_merge_commits` is now set to `true` by default. The feature to ignore squash commits was introduced in `v9.18.0` and was originally set to `false` to prevent unexpected results on a non-breaking update. The ignore merge commits feature prevents additional unnecessary processing on a commit message that likely will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should they). The larger issue with merge commits is that they ultimately are a full copy of all the changes that were previously created and committed. The merge commit itself ensures that the previous commit tree is maintained in history, therefore the commit message always exists. If merge commits are parsed, it generally creates duplicate messages that will end up in your changelog, which is less than desired in most cases. If you have previously used the `changelog.exclude_commit_patterns` functionality to ignore merge commit messages then you will want this setting set to `true` to improve parsing speed. You can also now remove the merge commit exclude pattern from the list as well to improve parsing speed. If this functionality is not desired, you will need to update your configuration to change the new setting to `false`. --- src/semantic_release/commit_parser/scipy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index 3615fff94..ba5a2f358 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -150,8 +150,7 @@ class ScipyParserOptions(ParserOptions): parse_squash_commits: bool = True """Toggle flag for whether or not to parse squash commits""" - # TODO: breaking change v10, change default to True - ignore_merge_commits: bool = False + ignore_merge_commits: bool = True """Toggle flag for whether or not to ignore merge commits""" @property From 8a5152573b9175f01be06d0c4531ea0ca4de8dd4 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 17:20:36 -0600 Subject: [PATCH 051/120] feat(parser-emoji)!: set parser to ignore merge commits by default BREAKING CHANGE: The configuration setting `commit_parser_options.ignore_merge_commits` is now set to `true` by default. The feature to ignore squash commits was introduced in `v9.18.0` and was originally set to `false` to prevent unexpected results on a non-breaking update. The ignore merge commits feature prevents additional unnecessary processing on a commit message that likely will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should they). The larger issue with merge commits is that they ultimately are a full copy of all the changes that were previously created and committed. The merge commit itself ensures that the previous commit tree is maintained in history, therefore the commit message always exists. If merge commits are parsed, it generally creates duplicate messages that will end up in your changelog, which is less than desired in most cases. If you have previously used the `changelog.exclude_commit_patterns` functionality to ignore merge commit messages then you will want this setting set to `true` to improve parsing speed. You can also now remove the merge commit exclude pattern from the list as well to improve parsing speed. If this functionality is not desired, you will need to update your configuration to change the new setting to `false`. --- src/semantic_release/commit_parser/emoji.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/commit_parser/emoji.py b/src/semantic_release/commit_parser/emoji.py index 6fe965b02..81afaf4d7 100644 --- a/src/semantic_release/commit_parser/emoji.py +++ b/src/semantic_release/commit_parser/emoji.py @@ -97,8 +97,7 @@ class EmojiParserOptions(ParserOptions): parse_squash_commits: bool = True """Toggle flag for whether or not to parse squash commits""" - # TODO: breaking change v10, change default to True - ignore_merge_commits: bool = False + ignore_merge_commits: bool = True """Toggle flag for whether or not to ignore merge commits""" @property From 92b42febba1a69f7bac2bacf465862b7c1106d32 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 10:59:53 -0600 Subject: [PATCH 052/120] ci(validate): increase defined terminal width for pytest results --- .github/workflows/validate.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index e5a9b842d..a55e468fd 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -180,6 +180,8 @@ jobs: - name: Test | Run pytest -m unit --comprehensive id: tests + env: + COLUMNS: 150 run: | pytest \ -vv \ @@ -248,6 +250,8 @@ jobs: - name: Test | Run pytest -m e2e --comprehensive id: tests + env: + COLUMNS: 150 run: | pytest \ -vv \ @@ -340,12 +344,15 @@ jobs: - name: Test | Run pytest -m e2e id: tests shell: pwsh + # env: # Required for GitPython to work on Windows because of getpass.getuser() # USERNAME: "runneradmin" + # COLUMNS: 150 # Because GHA is currently broken on Windows to pass these varables, we do it manually run: | $env:USERNAME = "runneradmin" + $env:COLUMNS = 150 pytest ` -vv ` -nauto ` From 2cbd3f13d54d1ee9b7f11f522613f0b90106cbe5 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 21:31:33 -0600 Subject: [PATCH 053/120] refactor(parser-conventional): ensures squash commits are interpreted correctly refactored to avoid interpreting a `fix: #123` with the start of another commit since the prefix is essentially the same. --- .../commit_parser/conventional.py | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/semantic_release/commit_parser/conventional.py b/src/semantic_release/commit_parser/conventional.py index 25c0ae207..b4eac746c 100644 --- a/src/semantic_release/commit_parser/conventional.py +++ b/src/semantic_release/commit_parser/conventional.py @@ -147,24 +147,23 @@ def __init__(self, options: ConventionalCommitParserOptions | None = None) -> No ) ) from err - self.commit_prefix = regexp( + self.commit_subject = regexp( str.join( "", [ f"^{commit_type_pattern.pattern}", r"(?:\((?P[^\n]+)\))?", - # TODO: remove ! support as it is not part of the angular commit spec (its part of conventional commits spec) r"(?P!)?:\s+", + r"(?P[^\n]+)", ], ) ) - self.re_parser = regexp( + self.commit_msg_pattern = regexp( str.join( "", [ - self.commit_prefix.pattern, - r"(?P[^\n]+)", + self.commit_subject.pattern, r"(?:\n\n(?P.+))?", # commit body ], ), @@ -268,7 +267,7 @@ def commit_body_components_separator( return accumulator def parse_message(self, message: str) -> ParsedMessageResult | None: - if not (parsed := self.re_parser.match(message)): + if not (parsed := self.commit_msg_pattern.match(message)): return None parsed_break = parsed.group("break") @@ -467,25 +466,31 @@ def _find_squashed_commits_in_str(self, message: str) -> list[str]: if not clean_paragraph.strip(): continue - # Check if the paragraph is the start of a new angular commit - if not self.commit_prefix.search(clean_paragraph): - if not separate_commit_msgs and not current_msg: - # if there are no separate commit messages and no current message - # then this is the first commit message - current_msg = dedent(clean_paragraph) - continue - - # append the paragraph as part of the previous commit message + # Check if the paragraph is the start of a new conventional commit + # Note: that we check that the subject has more than one word to differentiate from + # a closing footer (e.g. "fix: #123", or "fix: ABC-123") + if (match := self.commit_subject.search(clean_paragraph)) and len( + match.group("subject").split(" ") + ) > 1: + # Since we found the start of the new commit, store any previous commit + # message separately and start the new commit message if current_msg: - current_msg += f"\n\n{dedent(clean_paragraph)}" - # else: drop the paragraph + separate_commit_msgs.append(current_msg) + + current_msg = clean_paragraph + continue + + if not separate_commit_msgs and not current_msg: + # if there are no separate commit messages and no current message + # then this is the first commit message + current_msg = dedent(clean_paragraph) continue - # Since we found the start of the new commit, store any previous commit - # message separately and start the new commit message + # append the paragraph as part of the previous commit message if current_msg: - separate_commit_msgs.append(current_msg) + current_msg += f"\n\n{dedent(clean_paragraph)}" - current_msg = clean_paragraph + # else: drop the paragraph + continue return [*separate_commit_msgs, current_msg] From 8c50c4f603a3479ca366a63556c0c35f0c84cf09 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 00:28:32 -0600 Subject: [PATCH 054/120] refactor(parser-scipy): ensure that scopes are properly extracted from commit subjects --- src/semantic_release/commit_parser/scipy.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index ba5a2f358..6083d7359 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -111,16 +111,18 @@ class ScipyParserOptions(ParserOptions): just with different tag names. """ - major_tags: Tuple[str, ...] = ("API",) + major_tags: Tuple[str, ...] = ("API", "DEP") """Commit-type prefixes that should result in a major release bump.""" - minor_tags: Tuple[str, ...] = ("DEP", "DEV", "ENH", "REV", "FEAT") + minor_tags: Tuple[str, ...] = ("ENH", "FEAT") """Commit-type prefixes that should result in a minor release bump.""" patch_tags: Tuple[str, ...] = ("BLD", "BUG", "MAINT") """Commit-type prefixes that should result in a patch release bump.""" other_allowed_tags: Tuple[str, ...] = ( + # "REV", # Revert commits are NOT Currently Supported + "DEV", "BENCH", "DOC", "STY", @@ -207,13 +209,13 @@ def __init__(self, options: ScipyParserOptions | None = None) -> None: "", [ f"^{commit_type_pattern.pattern}", - r"(?:\((?P[^\n]+)\))?", - r":\s+", + r"(?::[\t ]*(?P[^:\n]+))?", + r":[\t ]+", ], ) ) - self.re_parser = regexp( + self.commit_msg_pattern = regexp( str.join( "", [ @@ -318,7 +320,7 @@ def commit_body_components_separator( return accumulator def parse_message(self, message: str) -> ParsedMessageResult | None: - if not (parsed := self.re_parser.match(message)): + if not (parsed := self.commit_msg_pattern.match(message)): return None parsed_scope = parsed.group("scope") or "" From 48b3de0af2ac4f0f0d258a5d39c5cc960c314df1 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 18:26:20 -0600 Subject: [PATCH 055/120] refactor(parser-emoji): ensures that merge request numbers are removed from subject lines --- src/semantic_release/commit_parser/emoji.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/semantic_release/commit_parser/emoji.py b/src/semantic_release/commit_parser/emoji.py index 81afaf4d7..2199c6948 100644 --- a/src/semantic_release/commit_parser/emoji.py +++ b/src/semantic_release/commit_parser/emoji.py @@ -268,7 +268,9 @@ def commit_body_components_separator( return accumulator def parse_message(self, message: str) -> ParsedMessageResult: - subject = message.split("\n", maxsplit=1)[0] + msg_parts = message.split("\n", maxsplit=1) + subject = msg_parts[0] + msg_body = msg_parts[1] if len(msg_parts) > 1 else "" linked_merge_request = "" if mr_match := self.mr_selector.search(subject): @@ -287,7 +289,10 @@ def parse_message(self, message: str) -> ParsedMessageResult: # All emojis will remain part of the returned description body_components: dict[str, list[str]] = reduce( self.commit_body_components_separator, - parse_paragraphs(message), + [ + subject, + *parse_paragraphs(msg_body), + ], { "descriptions": [], "notices": [], @@ -302,11 +307,9 @@ def parse_message(self, message: str) -> ParsedMessageResult: type=primary_emoji, category=primary_emoji, scope=parsed_scope, - # TODO: breaking change v10, removes breaking change footers from descriptions - # descriptions=( - # descriptions[:1] if level_bump is LevelBump.MAJOR else descriptions - # ) - descriptions=descriptions, + descriptions=( + descriptions[:1] if level_bump is LevelBump.MAJOR else descriptions + ), breaking_descriptions=( descriptions[1:] if level_bump is LevelBump.MAJOR else () ), From eb0a9eb8af6a4af6f5ff17b135be5a01e722d95a Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 20:27:58 -0600 Subject: [PATCH 056/120] test(conftest): update commit object creation to prevent test failures --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index 933a0cfd1..2897bbac7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -396,6 +396,7 @@ def _make_commit(message: str) -> Commit: authored_date=commit_timestamp, committer=commit_author, committed_date=commit_timestamp, + parents=[], ) return _make_commit From 802c1902ac4856ee470cf3d7526db27c71142b19 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 21:32:32 -0600 Subject: [PATCH 057/120] test(parser-conventional): update unit testing to match expected output of modified parser --- .../commit_parser/test_conventional.py | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/tests/unit/semantic_release/commit_parser/test_conventional.py b/tests/unit/semantic_release/commit_parser/test_conventional.py index 078e1ecd5..02cd4f5de 100644 --- a/tests/unit/semantic_release/commit_parser/test_conventional.py +++ b/tests/unit/semantic_release/commit_parser/test_conventional.py @@ -1,3 +1,4 @@ +# ruff: noqa: SIM300 from __future__ import annotations from textwrap import dedent @@ -80,7 +81,6 @@ def test_parser_raises_unknown_message_style( "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -127,7 +127,6 @@ def test_parser_raises_unknown_message_style( "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -154,8 +153,6 @@ def test_parser_raises_unknown_message_style( "scope": "cli", "descriptions": [ "changed option name", - "BREAKING CHANGE: A breaking change description", - "Closes: #555", # This is a bit unusual but its because there is no identifier that will # identify this as a separate commit so it gets included in the previous commit "invalid non-conventional formatted commit", @@ -255,7 +252,6 @@ def test_parser_squashed_commit_bitbucket_squash_style( "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -320,7 +316,6 @@ def test_parser_squashed_commit_bitbucket_squash_style( "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -344,8 +339,6 @@ def test_parser_squashed_commit_bitbucket_squash_style( "scope": "cli", "descriptions": [ "changed option name", - "BREAKING CHANGE: A breaking change description", - "Closes: #555", ], "breaking_descriptions": [ "A breaking change description", @@ -432,11 +425,9 @@ def test_parser_squashed_commit_git_squash_style( "type": "bug fixes", "scope": "release-config", "descriptions": [ - # TODO: v10 removal of PR number from subject - "some commit subject (#10)", + "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -477,11 +468,9 @@ def test_parser_squashed_commit_git_squash_style( "type": "bug fixes", "scope": "release-config", "descriptions": [ - # TODO: v10 removal of PR number from subject - "some commit subject (#10)", + "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -508,8 +497,6 @@ def test_parser_squashed_commit_git_squash_style( "scope": "cli", "descriptions": [ "changed option name", - "BREAKING CHANGE: A breaking change description", - "Closes: #555", # This is a bit unusual but its because there is no identifier that will # identify this as a separate commit so it gets included in the previous commit "* invalid non-conventional formatted commit", @@ -689,7 +676,7 @@ def test_parser_return_scope_from_commit_message( ), ( f"fix(tox): fix env \n\n{_long_text}\n\n{_footer}", - ["fix env ", _long_text, _footer], + ["fix env ", _long_text], ), ("fix: superfix", ["superfix"]), ], @@ -717,23 +704,23 @@ def test_parser_return_subject_from_commit_message( # GitHub, Gitea style ( "feat(parser): add emoji parser (#123)", - "add emoji parser (#123)", + "add emoji parser", "#123", ), # GitLab style ( "fix(parser): fix regex in conventional parser (!456)", - "fix regex in conventional parser (!456)", + "fix regex in conventional parser", "!456", ), # BitBucket style ( "feat(parser): add emoji parser (pull request #123)", - "add emoji parser (pull request #123)", + "add emoji parser", "#123", ), # Both a linked merge request and an issue footer (should return the linked merge request) - ("fix: superfix (#123)\n\nCloses: #400", "superfix (#123)", "#123"), + ("fix: superfix (#123)\n\nCloses: #400", "superfix", "#123"), # None ("fix: superfix", "superfix", ""), # None but includes an issue footer it should not be considered a linked merge request @@ -760,7 +747,6 @@ def test_parser_return_linked_merge_request_from_commit_message( @pytest.mark.parametrize( "message, linked_issues", - # TODO: in v10, we will remove the issue reference footers from the descriptions [ *[ # GitHub, Gitea, GitLab style @@ -1040,7 +1026,7 @@ def test_parser_return_linked_issues_from_commit_message( parsed_results = default_conventional_parser.parse(make_commit_obj(message)) assert isinstance(parsed_results, Iterable) - assert len(parsed_results) == 1 + assert 1 == len(parsed_results) result = next(iter(parsed_results)) assert isinstance(result, ParsedCommit) From 261164e457ad5e855638f9e8169a6c924844ebd3 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 21:35:45 -0600 Subject: [PATCH 058/120] test(parser-scipy): update unit testing to match expected output of modified parser --- tests/fixtures/scipy.py | 6 +- .../commit_parser/test_scipy.py | 570 ++++++++++++------ 2 files changed, 400 insertions(+), 176 deletions(-) diff --git a/tests/fixtures/scipy.py b/tests/fixtures/scipy.py index f9c704090..3d7b07127 100644 --- a/tests/fixtures/scipy.py +++ b/tests/fixtures/scipy.py @@ -99,6 +99,7 @@ def scipy_nonparseable_commits() -> list[str]: def scipy_chore_subjects(scipy_chore_commit_types: list[str]) -> list[str]: subjects = { "BENCH": "disable very slow benchmark in optimize_milp.py", + "DEV": "add unicode check to pre-commit-hook", "DOC": "change approx_fprime doctest (#20568)", "STY": "fixed ruff & mypy issues", "TST": "Skip Cython tests for editable installs", @@ -125,10 +126,8 @@ def scipy_patch_subjects(scipy_patch_commit_types: list[str]) -> list[str]: @pytest.fixture(scope="session") def scipy_minor_subjects(scipy_minor_commit_types: list[str]) -> list[str]: subjects = { - "DEP": "stats: switch kendalltau to kwarg-only, remove initial_lexsort", - "DEV": "add unicode check to pre-commit-hook", "ENH": "stats.ttest_1samp: add array-API support (#20545)", - "REV": "reverted a previous commit", + # "REV": "reverted a previous commit", "FEAT": "added a new feature", } # Test fixture modification failure prevention @@ -140,6 +139,7 @@ def scipy_minor_subjects(scipy_minor_commit_types: list[str]) -> list[str]: def scipy_major_subjects(scipy_major_commit_types: list[str]) -> list[str]: subjects = { "API": "dropped support for python 3.7", + "DEP": "stats: switch kendalltau to kwarg-only, remove initial_lexsort", } # Test fixture modification failure prevention assert len(subjects.keys()) == len(scipy_major_commit_types) diff --git a/tests/unit/semantic_release/commit_parser/test_scipy.py b/tests/unit/semantic_release/commit_parser/test_scipy.py index 2c15fca6f..46f70b211 100644 --- a/tests/unit/semantic_release/commit_parser/test_scipy.py +++ b/tests/unit/semantic_release/commit_parser/test_scipy.py @@ -1,3 +1,4 @@ +# ruff: noqa: SIM300 from __future__ import annotations from re import compile as regexp @@ -9,7 +10,6 @@ from semantic_release.commit_parser.scipy import ( ScipyCommitParser, ScipyParserOptions, - tag_to_section, ) from semantic_release.commit_parser.token import ParsedCommit, ParseError from semantic_release.enums import LevelBump @@ -37,163 +37,400 @@ def test_parser_raises_unknown_message_style( assert isinstance(result, ParseError) -def test_valid_scipy_parsed_chore_commits( - default_scipy_parser: ScipyCommitParser, - make_commit_obj: MakeCommitObjFn, - scipy_chore_commit_parts: list[tuple[str, str, list[str]]], - scipy_chore_commits: list[str], -): - expected_parts = scipy_chore_commit_parts - - for i, full_commit_msg in enumerate(scipy_chore_commits): - (commit_type, subject, commit_bodies) = expected_parts[i] - commit_bodies = [unwordwrap.sub(" ", body).rstrip() for body in commit_bodies] - expected_type = tag_to_section[commit_type] - expected_descriptions = [ - subject, - *[body.rstrip() for body in commit_bodies if body], - ] - expected_brk_desc: list[str] = [] - - commit = make_commit_obj(full_commit_msg) - parsed_results = default_scipy_parser.parse(commit) - assert isinstance(parsed_results, Iterable) - - result = next(iter(parsed_results)) - assert isinstance(result, ParsedCommit) - assert LevelBump.NO_RELEASE is result.bump - assert expected_type == result.type - assert expected_descriptions == result.descriptions - assert expected_brk_desc == result.breaking_descriptions - assert not result.scope +@pytest.mark.parametrize( + "commit_message, expected_commit_details", + [ + pytest.param( + commit_message, + expected_commit_details, + id=test_id, + ) + for test_id, commit_message, expected_commit_details in [ + ( + "Chore Type: Benchmark related", + dedent( + """\ + BENCH:optimize_milp.py: add new benchmark + Benchmarks the performance of the MILP solver + """ + ), + { + "bump": LevelBump.NO_RELEASE, + "type": "none", + "scope": "optimize_milp.py", + "descriptions": [ + "add new benchmark", + "Benchmarks the performance of the MILP solver", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "", + }, + ), + ( + "Chore Type: Dev Tool Related", + dedent( + """\ + DEV: add unicode check to pre-commit hook + """ + ), + { + "bump": LevelBump.NO_RELEASE, + "type": "none", + "scope": "", + "descriptions": [ + "add unicode check to pre-commit hook", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "", + }, + ), + ( + "Chore Type: documentation related", + dedent( + """\ + DOC: change approx_fprime doctest (#20568) + """ + ), + { + "bump": LevelBump.NO_RELEASE, + "type": "documentation", + "scope": "", + "descriptions": [ + "change approx_fprime doctest", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "#20568", + }, + ), + ( + "Chore Type: style related", + dedent( + """\ + STY: fixed ruff & mypy issues + """ + ), + { + "bump": LevelBump.NO_RELEASE, + "type": "none", + "scope": "", + "descriptions": [ + "fixed ruff & mypy issues", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "", + }, + ), + ( + "Chore Type: Test related", + dedent( + """\ + TST: Skip Cython tests for editable installs + """ + ), + { + "bump": LevelBump.NO_RELEASE, + "type": "none", + "scope": "", + "descriptions": [ + "Skip Cython tests for editable installs", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "", + }, + ), + ( + "Chore Type: Test related", + dedent( + """\ + TEST: Skip Cython tests for editable installs + """ + ), + { + "bump": LevelBump.NO_RELEASE, + "type": "none", + "scope": "", + "descriptions": [ + "Skip Cython tests for editable installs", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "", + }, + ), + ( + "Chore Type: Release related", + dedent( + """\ + REL: set version to 1.0.0 + """ + ), + { + "bump": LevelBump.NO_RELEASE, + "type": "none", + "scope": "", + "descriptions": [ + "set version to 1.0.0", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "", + }, + ), + ( + "Patch Type: Build related", + dedent( + """\ + BLD: move the optimize build steps earlier into the build sequence + """ + ), + { + "bump": LevelBump.PATCH, + "type": "fix", + "scope": "", + "descriptions": [ + "move the optimize build steps earlier into the build sequence", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "", + }, + ), + ( + "Patch Type: Bug fix", + dedent( + """\ + BUG: Fix invalid default bracket selection in _bracket_minimum (#20563) + """ + ), + { + "bump": LevelBump.PATCH, + "type": "fix", + "scope": "", + "descriptions": [ + "Fix invalid default bracket selection in _bracket_minimum", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "#20563", + }, + ), + ( + "Patch Type: Maintenance", + dedent( + """\ + MAINT: optimize.linprog: fix bug when integrality is a list of all zeros (#20586) -def test_valid_scipy_parsed_patch_commits( - default_scipy_parser: ScipyCommitParser, - make_commit_obj: MakeCommitObjFn, - scipy_patch_commit_parts: list[tuple[str, str, list[str]]], - scipy_patch_commits: list[str], -): - expected_parts = scipy_patch_commit_parts - - for i, full_commit_msg in enumerate(scipy_patch_commits): - (commit_type, subject, commit_bodies) = expected_parts[i] - commit_bodies = [unwordwrap.sub(" ", body).rstrip() for body in commit_bodies] - expected_type = tag_to_section[commit_type] - expected_descriptions = [ - subject, - *[body.rstrip() for body in commit_bodies if body], - ] - expected_brk_desc: list[str] = [] + This is a bug fix for the linprog function in the optimize module. - commit = make_commit_obj(full_commit_msg) - parsed_results = default_scipy_parser.parse(commit) - assert isinstance(parsed_results, Iterable) + Closes: #555 + """ + ), + { + "bump": LevelBump.PATCH, + "type": "fix", + "scope": "optimize.linprog", + "descriptions": [ + "fix bug when integrality is a list of all zeros", + "This is a bug fix for the linprog function in the optimize module.", + ], + "breaking_descriptions": [], + "linked_issues": ("#555",), + "linked_merge_request": "#20586", + }, + ), + ( + "Feature Type: Enhancement", + dedent( + """\ + ENH: stats.ttest_1samp: add array-API support (#20545) - result = next(iter(parsed_results)) - assert isinstance(result, ParsedCommit) - assert LevelBump.PATCH is result.bump - assert expected_type == result.type - assert expected_descriptions == result.descriptions - assert expected_brk_desc == result.breaking_descriptions - assert not result.scope + Closes: #1444 + """ + ), + { + "bump": LevelBump.MINOR, + "type": "feature", + "scope": "stats.ttest_1samp", + "descriptions": [ + "add array-API support", + ], + "breaking_descriptions": [], + "linked_issues": ("#1444",), + "linked_merge_request": "#20545", + }, + ), + # ( + # NOT CURRENTLY SUPPORTED + # "Feature Type: Revert", + # dedent( + # """\ + # REV: revert "ENH: add new feature (#20545)" + # This reverts commit 63ec09b9e844e616dcaa7bae35a0b66671b59fbb. + # """ + # ), + # { + # "bump": LevelBump.MINOR, + # "type": "other", + # "scope": "", + # "descriptions": [ + # 'revert "ENH: add new feature (#20545)"', + # "This reverts commit 63ec09b9e844e616dcaa7bae35a0b66671b59fbb.", + # ], + # "breaking_descriptions": [], + # "linked_issues": (), + # "linked_merge_request": "", + # }, + # ), + ( + "Feature Type: FEAT", + dedent( + """\ + FEAT: add new feature (#20545) + """ + ), + { + "bump": LevelBump.MINOR, + "type": "feature", + "scope": "", + "descriptions": [ + "add new feature", + ], + "breaking_descriptions": [], + "linked_issues": (), + "linked_merge_request": "#20545", + }, + ), + ( + "Breaking Type: API", + dedent( + """\ + API: dropped support for Python 3.7 + Users of Python 3.7 should use version 1.0.0 or try to upgrade to Python 3.8 + or later to continue using this package. + """ + ), + { + "bump": LevelBump.MAJOR, + "type": "breaking", + "scope": "", + "descriptions": [ + "dropped support for Python 3.7", + ], + "breaking_descriptions": [ + "Users of Python 3.7 should use version 1.0.0 or try to upgrade to Python 3.8 or later to continue using this package.", + ], + "linked_issues": (), + "linked_merge_request": "", + }, + ), + ( + "Breaking Type: Deprecate", + dedent( + """\ + DEP: deprecated the limprog function -def test_valid_scipy_parsed_minor_commits( + The linprog function is deprecated and will be removed in a future release. + Use the new linprog2 function instead. + """ + ), + { + "bump": LevelBump.MAJOR, + "type": "breaking", + "scope": "", + "descriptions": [ + "deprecated the limprog function", + ], + "breaking_descriptions": [ + "The linprog function is deprecated and will be removed in a future release. Use the new linprog2 function instead.", + ], + "linked_issues": (), + "linked_merge_request": "", + }, + ), + ] + ], +) +def test_scipy_parser_parses_commit_message( default_scipy_parser: ScipyCommitParser, make_commit_obj: MakeCommitObjFn, - scipy_minor_commit_parts: list[tuple[str, str, list[str]]], - scipy_minor_commits: list[str], + commit_message: str, + expected_commit_details: dict | None, ): - expected_parts = scipy_minor_commit_parts - - for i, full_commit_msg in enumerate(scipy_minor_commits): - (commit_type, subject, commit_bodies) = expected_parts[i] - commit_bodies = [unwordwrap.sub(" ", body).rstrip() for body in commit_bodies] - expected_type = tag_to_section[commit_type] - expected_descriptions = [ - subject, - *[body for body in commit_bodies if body], - ] - expected_brk_desc: list[str] = [] - - commit = make_commit_obj(full_commit_msg) - parsed_results = default_scipy_parser.parse(commit) - assert isinstance(parsed_results, Iterable) - - result = next(iter(parsed_results)) - assert isinstance(result, ParsedCommit) - assert LevelBump.MINOR is result.bump - assert expected_type == result.type - assert expected_descriptions == result.descriptions - assert expected_brk_desc == result.breaking_descriptions - assert not result.scope + # Setup: Enable squash commit parsing + parser = ScipyCommitParser( + options=ScipyParserOptions( + **{ + **default_scipy_parser.options.__dict__, + "parse_squash_commits": False, + } + ) + ) + # Build the commit object and parse it + the_commit = make_commit_obj(commit_message) + parsed_results = parser.parse(the_commit) -def test_valid_scipy_parsed_major_commits( - default_scipy_parser: ScipyCommitParser, - make_commit_obj: MakeCommitObjFn, - scipy_major_commit_parts: list[tuple[str, str, list[str]]], - scipy_major_commits: list[str], -): - expected_parts = scipy_major_commit_parts - - for i, full_commit_msg in enumerate(scipy_major_commits): - (commit_type, subject, commit_bodies) = expected_parts[i] - commit_bodies = [unwordwrap.sub(" ", body).rstrip() for body in commit_bodies] - expected_type = tag_to_section[commit_type] - expected_descriptions = [ - subject, - *[body for body in commit_bodies if body], - ] - brkg_prefix = "BREAKING CHANGE: " - expected_brk_desc = [ - # TODO: Python 3.8 limitation, change to removeprefix() for 3.9+ - block[block.startswith(brkg_prefix) and len(brkg_prefix) :] - # block.removeprefix("BREAKING CHANGE: ") - for block in commit_bodies - if block.startswith("BREAKING CHANGE") - ] + # Validate the results + assert isinstance(parsed_results, Iterable) + assert 1 == len( + parsed_results + ), f"Expected 1 parsed result, but got {len(parsed_results)}" - commit = make_commit_obj(full_commit_msg) - parsed_results = default_scipy_parser.parse(commit) + result = next(iter(parsed_results)) - assert isinstance(parsed_results, Iterable) - assert len(parsed_results) == 1 + if expected_commit_details is None: + assert isinstance(result, ParseError) + return - result = next(iter(parsed_results)) - assert isinstance(result, ParsedCommit) - assert LevelBump.MAJOR is result.bump - assert expected_type == result.type - assert expected_descriptions == result.descriptions - assert expected_brk_desc == result.breaking_descriptions - assert not result.scope + assert isinstance(result, ParsedCommit) + # Required + assert expected_commit_details["bump"] == result.bump + assert expected_commit_details["type"] == result.type + # Optional + assert expected_commit_details.get("scope", "") == result.scope + # TODO: v11 change to tuples + assert expected_commit_details.get("descriptions", []) == result.descriptions + assert ( + expected_commit_details.get("breaking_descriptions", []) + == result.breaking_descriptions + ) + assert expected_commit_details.get("linked_issues", ()) == result.linked_issues + assert ( + expected_commit_details.get("linked_merge_request", "") + == result.linked_merge_request + ) @pytest.mark.parametrize( "message, subject, merge_request_number", - # TODO: in v10, we will remove the merge request number from the subject line [ # GitHub, Gitea style ( "ENH: add new feature (#123)", - "add new feature (#123)", + "add new feature", "#123", ), # GitLab style ( "BUG: fix regex in parser (!456)", - "fix regex in parser (!456)", + "fix regex in parser", "!456", ), # BitBucket style ( "ENH: add new feature (pull request #123)", - "add new feature (pull request #123)", + "add new feature", "#123", ), # Both a linked merge request and an issue footer (should return the linked merge request) - ("DEP: add dependency (#123)\n\nCloses: #400", "add dependency (#123)", "#123"), + ("DEP: add dependency (#123)\n\nCloses: #400", "add dependency", "#123"), # None ("BUG: superfix", "superfix", ""), # None but includes an issue footer it should not be considered a linked merge request @@ -233,7 +470,7 @@ def test_parser_return_linked_merge_request_from_commit_message( """\ Merged in feat/my-awesome-stuff (pull request #10) - BUG(release-config): some commit subject + BUG: release-config: some commit subject An additional description @@ -254,7 +491,6 @@ def test_parser_return_linked_merge_request_from_commit_message( "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -268,7 +504,7 @@ def test_parser_return_linked_merge_request_from_commit_message( """\ Merged in feat/my-awesome-stuff (pull request #10) - BUG(release-config): some commit subject + BUG:release-config: some commit subject An additional description @@ -280,11 +516,11 @@ def test_parser_return_linked_merge_request_from_commit_message( ENH: implemented searching gizmos by keyword - DOC(parser): add new parser pattern + DOC: parser: add new parser pattern - MAINT(cli)!: changed option name + API:cli: changed option name - BREAKING CHANGE: A breaking change description + A breaking change description Closes: #555 @@ -301,7 +537,6 @@ def test_parser_return_linked_merge_request_from_commit_message( "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -324,18 +559,16 @@ def test_parser_return_linked_merge_request_from_commit_message( }, { "bump": LevelBump.MAJOR, - "type": "fix", + "type": "breaking", "scope": "cli", "descriptions": [ "changed option name", - "BREAKING CHANGE: A breaking change description", - "Closes: #555", - # This is a bit unusual but its because there is no identifier that will - # identify this as a separate commit so it gets included in the previous commit - "invalid non-conventional formatted commit", ], "breaking_descriptions": [ "A breaking change description", + # This is a bit unusual but its because there is no identifier that will + # identify this as a separate commit so it gets included in the previous commit + "invalid non-conventional formatted commit", ], "linked_issues": ("#555",), "linked_merge_request": "#10", @@ -408,7 +641,7 @@ def test_parser_squashed_commit_bitbucket_squash_style( Author: author Date: Sun Jan 19 12:05:23 2025 +0000 - BUG(release-config): some commit subject + BUG: release-config: some commit subject An additional description @@ -429,7 +662,6 @@ def test_parser_squashed_commit_bitbucket_squash_style( "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -446,7 +678,7 @@ def test_parser_squashed_commit_bitbucket_squash_style( Author: author Date: Sun Jan 19 12:05:23 2025 +0000 - BUG(release-config): some commit subject + BUG: release-config: some commit subject An additional description @@ -466,15 +698,15 @@ def test_parser_squashed_commit_bitbucket_squash_style( Author: author Date: Sat Jan 18 10:13:53 2025 +0000 - DOC(parser): add new parser pattern + DOC: parser: add new parser pattern commit 5f0292fb5a88c3a46e4a02bec35b85f5228e8e51 Author: author Date: Sat Jan 18 10:13:53 2025 +0000 - MAINT(cli): changed option name + API:cli: changed option name - BREAKING CHANGE: A breaking change description + A breaking change description Closes: #555 @@ -494,7 +726,6 @@ def test_parser_squashed_commit_bitbucket_squash_style( "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -514,12 +745,10 @@ def test_parser_squashed_commit_bitbucket_squash_style( }, { "bump": LevelBump.MAJOR, - "type": "fix", + "type": "breaking", "scope": "cli", "descriptions": [ "changed option name", - "BREAKING CHANGE: A breaking change description", - "Closes: #555", ], "breaking_descriptions": [ "A breaking change description", @@ -589,7 +818,7 @@ def test_parser_squashed_commit_git_squash_style( "Single commit squashed via GitHub PR resolution", dedent( """\ - BUG(release-config): some commit subject (#10) + BUG: release-config: some commit subject (#10) An additional description @@ -606,10 +835,9 @@ def test_parser_squashed_commit_git_squash_style( "type": "fix", "scope": "release-config", "descriptions": [ - "some commit subject (#10)", + "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -621,7 +849,7 @@ def test_parser_squashed_commit_git_squash_style( "Multiple commits squashed via GitHub PR resolution", dedent( """\ - BUG(release-config): some commit subject (#10) + BUG: release-config: some commit subject (#10) An additional description @@ -633,13 +861,13 @@ def test_parser_squashed_commit_git_squash_style( * ENH: implemented searching gizmos by keyword - * DOC(parser): add new parser pattern + * DOC: parser: add new parser pattern - * MAINT(cli)!: changed option name + * API:cli: changed option name - BREAKING CHANGE: A breaking change description + A breaking change description - Closes: #555 + Closes: #555 * invalid non-conventional formatted commit """ @@ -650,11 +878,9 @@ def test_parser_squashed_commit_git_squash_style( "type": "fix", "scope": "release-config", "descriptions": [ - # TODO: v10 removal of PR number from subject - "some commit subject (#10)", + "some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -677,18 +903,16 @@ def test_parser_squashed_commit_git_squash_style( }, { "bump": LevelBump.MAJOR, - "type": "fix", + "type": "breaking", "scope": "cli", "descriptions": [ "changed option name", - "BREAKING CHANGE: A breaking change description", - "Closes: #555", - # This is a bit unusual but its because there is no identifier that will - # identify this as a separate commit so it gets included in the previous commit - "* invalid non-conventional formatted commit", ], "breaking_descriptions": [ "A breaking change description", + # This is a bit unusual but its because there is no identifier that will + # identify this as a separate commit so it gets included in the previous commit + "* invalid non-conventional formatted commit", ], "linked_issues": ("#555",), "linked_merge_request": "#10", @@ -866,7 +1090,7 @@ def test_parser_squashed_commit_github_squash_style( *[ # JIRA style ( - f"ENH(parser): add magic parser\n\n{footer}", + f"ENH: parser: add magic parser\n\n{footer}", linked_issues, ) for footer_prefix in SUPPORTED_ISSUE_CLOSURE_PREFIXES @@ -994,7 +1218,7 @@ def test_parser_squashed_commit_github_squash_style( ], *[ ( - f"ENH(parser): add magic parser\n\n{footer}", + f"ENH: parser: add magic parser\n\n{footer}", linked_issues, ) for footer, linked_issues in [ @@ -1006,13 +1230,13 @@ def test_parser_squashed_commit_github_squash_style( ], ( # Only grabs the issue reference when there is a GitHub PR reference in the subject - "ENH(parser): add magic parser (#123)\n\nCloses: #555", + "ENH: parser: add magic parser (#123)\n\nCloses: #555", ["#555"], ), # Does not grab an issue when there is only a GitHub PR reference in the subject - ("ENH(parser): add magic parser (#123)", []), + ("ENH: parser: add magic parser (#123)", []), # Does not grab an issue when there is only a Bitbucket PR reference in the subject - ("ENH(parser): add magic parser (pull request #123)", []), + ("ENH: parser: add magic parser (pull request #123)", []), ], ) def test_parser_return_linked_issues_from_commit_message( @@ -1044,7 +1268,7 @@ def test_parser_return_linked_issues_from_commit_message( "single notice", dedent( """\ - BUG(parser): fix regex in scipy parser + BUG:parser: fix regex in scipy parser NOTICE: This is a notice """ @@ -1055,7 +1279,7 @@ def test_parser_return_linked_issues_from_commit_message( "multiline notice", dedent( """\ - BUG(parser): fix regex in scipy parser + BUG:parser: fix regex in scipy parser NOTICE: This is a notice that is longer than other notices @@ -1067,7 +1291,7 @@ def test_parser_return_linked_issues_from_commit_message( "multiple notices", dedent( """\ - BUG(parser): fix regex in scipy parser + BUG:parser: fix regex in scipy parser NOTICE: This is a notice @@ -1080,9 +1304,9 @@ def test_parser_return_linked_issues_from_commit_message( "notice with other footer", dedent( """\ - BUG(parser): fix regex in scipy parser + BUG:parser: fix regex in scipy parser - BREAKING CHANGE: This is a breaking change + This is a breaking change NOTICE: This is a notice """ From f54e72b4af39f35cd5acc5e3f219d9fe2da94ed0 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 18:27:25 -0600 Subject: [PATCH 059/120] test(parser-emoji): update unit testing to match expected output of modified parser --- .../commit_parser/test_emoji.py | 50 +++++-------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/tests/unit/semantic_release/commit_parser/test_emoji.py b/tests/unit/semantic_release/commit_parser/test_emoji.py index ec2d83a3e..ac7708ebb 100644 --- a/tests/unit/semantic_release/commit_parser/test_emoji.py +++ b/tests/unit/semantic_release/commit_parser/test_emoji.py @@ -23,7 +23,7 @@ ":boom: Breaking changes\n\nMore description\n\nEven more description", LevelBump.MAJOR, ":boom:", - [":boom: Breaking changes", "More description", "Even more description"], + [":boom: Breaking changes"], ["More description", "Even more description"], ), # Minor bump @@ -63,7 +63,7 @@ ":sparkles: Add a new feature\n\n:boom: should not be detected", LevelBump.MINOR, ":sparkles:", - [":sparkles: Add a new feature", ":boom: should not be detected"], + [":sparkles: Add a new feature"], [], ), ], @@ -91,28 +91,27 @@ def test_default_emoji_parser( @pytest.mark.parametrize( "message, subject, merge_request_number", - # TODO: in v10, we will remove the merge request number from the subject line [ # GitHub, Gitea style ( ":sparkles: add new feature (#123)", - ":sparkles: add new feature (#123)", + ":sparkles: add new feature", "#123", ), # GitLab style ( ":bug: fix regex in parser (!456)", - ":bug: fix regex in parser (!456)", + ":bug: fix regex in parser", "!456", ), # BitBucket style ( ":sparkles: add new feature (pull request #123)", - ":sparkles: add new feature (pull request #123)", + ":sparkles: add new feature", "#123", ), # Both a linked merge request and an issue footer (should return the linked merge request) - (":bug: superfix (#123)\n\nCloses: #400", ":bug: superfix (#123)", "#123"), + (":bug: superfix (#123)\n\nCloses: #400", ":bug: superfix", "#123"), # None (":bug: superfix", ":bug: superfix", ""), # None but includes an issue footer it should not be considered a linked merge request @@ -547,9 +546,7 @@ def test_parser_return_release_notices_from_commit_message( { "bump": LevelBump.NO_RELEASE, "type": "Other", - "descriptions": [ - "Merged in feat/my-awesome-stuff (pull request #10)" - ], + "descriptions": ["Merged in feat/my-awesome-stuff"], "linked_merge_request": "#10", }, { @@ -560,7 +557,6 @@ def test_parser_return_release_notices_from_commit_message( ":bug:(release-config): some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -601,9 +597,7 @@ def test_parser_return_release_notices_from_commit_message( { "bump": LevelBump.NO_RELEASE, "type": "Other", - "descriptions": [ - "Merged in feat/my-awesome-stuff (pull request #10)" - ], + "descriptions": ["Merged in feat/my-awesome-stuff"], "linked_merge_request": "#10", }, { @@ -614,7 +608,6 @@ def test_parser_return_release_notices_from_commit_message( ":bug:(release-config): some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -643,15 +636,9 @@ def test_parser_return_release_notices_from_commit_message( "scope": "", "descriptions": [ ":boom::bug: changed option name", - "A breaking change description", - "Closes: #555", - # This is a bit unusual but its because there is no identifier that will - # identify this as a separate commit so it gets included in the previous commit - "invalid non-conventional formatted commit", ], "breaking_descriptions": [ "A breaking change description", - "Closes: #555", # This is a bit unusual but its because there is no identifier that will # identify this as a separate commit so it gets included in the previous commit "invalid non-conventional formatted commit", @@ -749,7 +736,6 @@ def test_parser_squashed_commit_bitbucket_squash_style( ":bug:(release-config): some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -814,7 +800,6 @@ def test_parser_squashed_commit_bitbucket_squash_style( ":bug:(release-config): some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -839,12 +824,9 @@ def test_parser_squashed_commit_bitbucket_squash_style( "type": ":boom:", "descriptions": [ ":boom::bug: changed option name", - "A breaking change description", - "Closes: #555", ], "breaking_descriptions": [ "A breaking change description", - "Closes: #555", ], "linked_issues": ("#555",), }, @@ -933,11 +915,9 @@ def test_parser_squashed_commit_git_squash_style( "type": ":bug:", "scope": "release-config", "descriptions": [ - # TODO: v10 removal of PR number from subject - ":bug:(release-config): some commit subject (#10)", + ":bug:(release-config): some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -978,11 +958,9 @@ def test_parser_squashed_commit_git_squash_style( "type": ":bug:", "scope": "release-config", "descriptions": [ - # TODO: v10 removal of PR number from subject - ":bug:(release-config): some commit subject (#10)", + ":bug:(release-config): some commit subject", "An additional description", "Second paragraph with multiple lines that will be condensed", - "Resolves: #12", "Signed-off-by: author ", ], "linked_issues": ("#12",), @@ -1011,15 +989,11 @@ def test_parser_squashed_commit_git_squash_style( "scope": "", "descriptions": [ ":boom::bug: changed option name", - "A breaking change description", - "Closes: #555", - # This is a bit unusual but its because there is no identifier that will - # identify this as a separate commit so it gets included in the previous commit - "* invalid non-conventional formatted commit", ], "breaking_descriptions": [ "A breaking change description", - "Closes: #555", + # This is a bit unusual but its because there is no identifier that will + # identify this as a separate commit so it gets included in the previous commit "* invalid non-conventional formatted commit", ], "linked_issues": ("#555",), From 9d51b2a93907f85273944122e9158d3948b887fc Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 10:57:56 -0600 Subject: [PATCH 060/120] test(fixtures): update history creator fixture to new parser msg cleanup --- tests/fixtures/git_repo.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/git_repo.py b/tests/fixtures/git_repo.py index 048f77b3a..c1796a364 100644 --- a/tests/fixtures/git_repo.py +++ b/tests/fixtures/git_repo.py @@ -167,6 +167,7 @@ def __call__( self, build_definition: Sequence[RepoActions], filter_4_changelog: bool = False, + ignore_merge_commits: bool = False, ) -> RepoDefinition: ... RepoDefinition: TypeAlias = dict[VersionStr, RepoVersionDef] # type: ignore[misc] # mypy is thoroughly confused @@ -1470,6 +1471,7 @@ def get_commits_from_repo_build_def() -> GetCommitsFromRepoBuildDefFn: def _get_commits( build_definition: Sequence[RepoActions], filter_4_changelog: bool = False, + ignore_merge_commits: bool = False, ) -> RepoDefinition: # Extract the commits from the build definition repo_def: RepoDefinition = {} @@ -1494,7 +1496,14 @@ def _get_commits( if "commit_def" in build_step["details"]: commit_def = build_step["details"]["commit_def"] # type: ignore[typeddict-item] - if filter_4_changelog and not commit_def["include_in_changelog"]: + if any( + ( + ignore_merge_commits + and build_step["action"] == RepoActionStep.GIT_MERGE, + filter_4_changelog + and not commit_def["include_in_changelog"], + ) + ): continue commits.append(commit_def) From 68e39a81b6d1e935948815483ee6e022fa40c4b3 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 10:59:13 -0600 Subject: [PATCH 061/120] test(release-history): update test to handle new default `ignore_merge_commits` setting --- .../changelog/test_release_history.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/unit/semantic_release/changelog/test_release_history.py b/tests/unit/semantic_release/changelog/test_release_history.py index 17b327cfa..2f45bbfb8 100644 --- a/tests/unit/semantic_release/changelog/test_release_history.py +++ b/tests/unit/semantic_release/changelog/test_release_history.py @@ -74,10 +74,7 @@ def _create_release_history_from_repo_def( if commit["category"] not in commits_per_group: commits_per_group[commit["category"]] = [] - commits_per_group[commit["category"]].append( - # TODO: remove the newline when our release history strips whitespace from commit messages - commit["msg"].strip() + "\n" - ) + commits_per_group[commit["category"]].append(commit["msg"].strip()) if version_str == "Unreleased": unreleased_history = commits_per_group @@ -87,7 +84,9 @@ def _create_release_history_from_repo_def( version = Version.parse(version_str) # add the PSR version commit message - commits_per_group["Unknown"].append(COMMIT_MESSAGE.format(version=version)) + commits_per_group["Unknown"].append( + COMMIT_MESSAGE.format(version=version).strip() + ) # store the organized commits for this version released_history[version] = commits_per_group @@ -132,7 +131,10 @@ def test_release_history( ): repo = repo_result["repo"] expected_release_history = create_release_history_from_repo_def( - get_commits_from_repo_build_def(repo_result["definition"]) + get_commits_from_repo_build_def( + repo_result["definition"], + ignore_merge_commits=default_conventional_parser.options.ignore_merge_commits, + ) ) expected_released_versions = sorted( map(str, expected_release_history.released.keys()) @@ -179,7 +181,7 @@ def test_release_history( "\n---\n", sorted( [ - msg + str(msg).strip() for bucket in [ CONVENTIONAL_COMMITS_MINOR[::-1], *expected_release_history.unreleased.values(), From c9a8ecfb746e06e9c0ea78495d4f6cf3414323e2 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 11:22:09 -0600 Subject: [PATCH 062/120] test(fixtures): update changelog generator to remove commit bodies --- tests/fixtures/git_repo.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/fixtures/git_repo.py b/tests/fixtures/git_repo.py index c1796a364..1c6a56207 100644 --- a/tests/fixtures/git_repo.py +++ b/tests/fixtures/git_repo.py @@ -1685,10 +1685,11 @@ def build_version_entry_markdown( else: commit_cl_desc = f"{commit_cl_desc} {sha_link}\n" - if len(descriptions) > 1: - commit_cl_desc += ( - "\n" + str.join("\n\n", [*descriptions[1:]]) + "\n" - ) + # COMMENTED out for v10 as the defualt changelog now only writes the subject line + # if len(descriptions) > 1: + # commit_cl_desc += ( + # "\n" + str.join("\n\n", [*descriptions[1:]]) + "\n" + # ) # Add commits to section if commit_cl_desc not in section_bullets: @@ -1798,10 +1799,11 @@ def build_version_entry_restructured_text( else: commit_cl_desc = f"{commit_cl_desc} {sha_link}\n" - if len(descriptions) > 1: - commit_cl_desc += ( - "\n" + str.join("\n\n", [*descriptions[1:]]) + "\n" - ) + # COMMENTED out for v10 as the defualt changelog now only writes the subject line + # if len(descriptions) > 1: + # commit_cl_desc += ( + # "\n" + str.join("\n\n", [*descriptions[1:]]) + "\n" + # ) # Add commits to section if commit_cl_desc not in section_bullets: From 6e2631cbfed29366603d169561b12a76719975e8 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 11:39:55 -0600 Subject: [PATCH 063/120] test(fixtures): update commit object creator as PSR now removes MRs from subject lines --- tests/fixtures/git_repo.py | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/tests/fixtures/git_repo.py b/tests/fixtures/git_repo.py index 1c6a56207..9601f44fe 100644 --- a/tests/fixtures/git_repo.py +++ b/tests/fixtures/git_repo.py @@ -534,15 +534,11 @@ def _get_commit_def_of_conventional_commit(msg: str) -> CommitDef: "include_in_changelog": False, } - descriptions = list(parsed_result.descriptions) - if parsed_result.linked_merge_request: - descriptions[0] = str.join("(", descriptions[0].split("(")[:-1]).strip() - return { "msg": msg, "type": parsed_result.type, "category": parsed_result.category, - "desc": str.join("\n\n", descriptions), + "desc": str.join("\n\n", parsed_result.descriptions), "brking_desc": str.join("\n\n", parsed_result.breaking_descriptions), "scope": parsed_result.scope, "mr": parsed_result.linked_merge_request, @@ -571,15 +567,11 @@ def _get_commit_def_of_emoji_commit(msg: str) -> CommitDef: "include_in_changelog": False, } - descriptions = list(parsed_result.descriptions) - if parsed_result.linked_merge_request: - descriptions[0] = str.join("(", descriptions[0].split("(")[:-1]).strip() - return { "msg": msg, "type": parsed_result.type, "category": parsed_result.category, - "desc": str.join("\n\n", descriptions), + "desc": str.join("\n\n", parsed_result.descriptions), "brking_desc": str.join("\n\n", parsed_result.breaking_descriptions), "scope": parsed_result.scope, "mr": parsed_result.linked_merge_request, @@ -608,15 +600,11 @@ def _get_commit_def_of_scipy_commit(msg: str) -> CommitDef: "include_in_changelog": False, } - descriptions = list(parsed_result.descriptions) - if parsed_result.linked_merge_request: - descriptions[0] = str.join("(", descriptions[0].split("(")[:-1]).strip() - return { "msg": msg, "type": parsed_result.type, "category": parsed_result.category, - "desc": str.join("\n\n", descriptions), + "desc": str.join("\n\n", parsed_result.descriptions), "brking_desc": str.join("\n\n", parsed_result.breaking_descriptions), "scope": parsed_result.scope, "mr": parsed_result.linked_merge_request, @@ -1145,21 +1133,7 @@ def _separate_squashed_commit_def( "msg": squashed_message, "type": parsed_result.type, "category": parsed_result.category, - "desc": str.join( - "\n\n", - ( - [ - # Strip out any MR references (since v9 doesn't) to prep for changelog generatro - # TODO: remove in v10, as the parser will remove the MR reference - str.join( - "(", parsed_result.descriptions[0].split("(")[:-1] - ).strip(), - *parsed_result.descriptions[1:], - ] - if parsed_result.linked_merge_request - else [*parsed_result.descriptions] - ), - ), + "desc": str.join("\n\n", parsed_result.descriptions), "brking_desc": str.join("\n\n", parsed_result.breaking_descriptions), "scope": parsed_result.scope, "mr": parsed_result.linked_merge_request or squashed_commit_def["mr"], From fee2ff9c331b740aabd3fac8c2a625849fd5e5a4 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 11:40:47 -0600 Subject: [PATCH 064/120] test(fixtures): update repo definitions with valid scipy scopes --- .../git_flow/repo_w_1_release_channel.py | 16 +++++----- .../git_flow/repo_w_2_release_channels.py | 28 ++++++++-------- .../git_flow/repo_w_3_release_channels.py | 32 +++++++++---------- .../git_flow/repo_w_4_release_channels.py | 26 +++++++-------- .../github_flow/repo_w_default_release.py | 8 ++--- .../github_flow/repo_w_release_channels.py | 14 ++++---- .../repo_w_dual_version_support.py | 12 +++---- ...po_w_dual_version_support_w_prereleases.py | 16 +++++----- .../trunk_based_dev/repo_w_prereleases.py | 12 +++---- .../repos/trunk_based_dev/repo_w_tags.py | 6 ++-- 10 files changed, 85 insertions(+), 85 deletions(-) diff --git a/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py b/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py index c624a7965..36a2eb3d1 100644 --- a/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py +++ b/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py @@ -111,7 +111,7 @@ def _get_repo_from_defintion( ) # Common static actions or components - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -295,7 +295,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -382,7 +382,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -429,7 +429,7 @@ def _get_repo_from_defintion( "\n\n", [ "API: add revolutionary feature", - "BREAKING CHANGE: this is a breaking change", + "This is a breaking change", ], ), "datetime": next(commit_timestamp_gen), @@ -487,7 +487,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -574,7 +574,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -661,7 +661,7 @@ def _get_repo_from_defintion( { "conventional": "feat(cli): add new config cli command", "emoji": ":sparkles: (cli) add new config cli command", - "scipy": "ENH(cli): add new config cli command", + "scipy": "ENH: cli: add new config cli command", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -717,7 +717,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], diff --git a/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py index f4a6005bc..22ccd7083 100644 --- a/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py @@ -111,7 +111,7 @@ def _get_repo_from_defintion( ) # Common static actions or components - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -301,7 +301,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -351,7 +351,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -388,7 +388,7 @@ def _get_repo_from_defintion( "\n\n", [ "API: add revolutionary feature", - "BREAKING CHANGE: this is a breaking change", + "This is a breaking change", ], ), "datetime": next(commit_timestamp_gen), @@ -409,7 +409,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -486,7 +486,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -517,7 +517,7 @@ def _get_repo_from_defintion( { "conventional": "feat(cli): add new config cli command", "emoji": ":sparkles: (cli) add new config cli command", - "scipy": "ENH(cli): add new config cli command", + "scipy": "ENH: cli: add new config cli command", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -573,7 +573,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -604,7 +604,7 @@ def _get_repo_from_defintion( { "conventional": "fix(config): fixed configuration generation", "emoji": ":bug: (config) fixed configuration generation", - "scipy": "MAINT(config): fixed configuration generation", + "scipy": "MAINT:config: fixed configuration generation", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -660,7 +660,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -710,7 +710,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -731,14 +731,14 @@ def _get_repo_from_defintion( { "conventional": "fix(scope): correct some text", "emoji": ":bug: (scope) correct some text", - "scipy": "MAINT(scope): correct some text", + "scipy": "MAINT:scope: correct some text", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, { "conventional": "feat(scope): add some more text", "emoji": ":sparkles:(scope) add some more text", - "scipy": "ENH(scope): add some more text", + "scipy": "ENH: scope: add some more text", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -757,7 +757,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], diff --git a/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py index 10cc98ff8..ba54e64e8 100644 --- a/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py @@ -113,7 +113,7 @@ def _get_repo_from_defintion( ) # Common static actions or components - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -309,7 +309,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -359,7 +359,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -396,7 +396,7 @@ def _get_repo_from_defintion( "\n\n", [ "API: add revolutionary feature", - "BREAKING CHANGE: this is a breaking change", + "This is a breaking change", ], ), "datetime": next(commit_timestamp_gen), @@ -417,7 +417,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -470,7 +470,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -500,7 +500,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -531,7 +531,7 @@ def _get_repo_from_defintion( { "conventional": "feat(cli): add new config cli command", "emoji": ":sparkles: (cli) add new config cli command", - "scipy": "ENH(cli): add new config cli command", + "scipy": "ENH:cli: add new config cli command", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -550,7 +550,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -571,7 +571,7 @@ def _get_repo_from_defintion( { "conventional": "feat(config): add new config option", "emoji": ":sparkles: (config) add new config option", - "scipy": "ENH(config): add new config option", + "scipy": "ENH: config: add new config option", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -590,7 +590,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -650,7 +650,7 @@ def _get_repo_from_defintion( { "conventional": "fix(cli): fix config cli command", "emoji": ":bug: (cli) fix config cli command", - "scipy": "BUG(cli): fix config cli command", + "scipy": "BUG:cli: fix config cli command", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -699,7 +699,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -729,7 +729,7 @@ def _get_repo_from_defintion( { "conventional": "fix(config): fix config option", "emoji": ":bug: (config) fix config option", - "scipy": "BUG(config): fix config option", + "scipy": "BUG: config: fix config option", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -778,7 +778,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -808,7 +808,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], diff --git a/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py index d6abbb5df..f8128015a 100644 --- a/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py @@ -123,7 +123,7 @@ def _get_repo_from_defintion( ) # Common static actions or components - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -381,7 +381,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -413,7 +413,7 @@ def _get_repo_from_defintion( { "conventional": "fix(cli): fix config cli command", "emoji": ":bug: (cli) fix config cli command", - "scipy": "BUG(cli): fix config cli command", + "scipy": "BUG:cli: fix config cli command", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -462,7 +462,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -492,7 +492,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -523,7 +523,7 @@ def _get_repo_from_defintion( { "conventional": "fix(config): fix config option", "emoji": ":bug: (config) fix config option", - "scipy": "BUG(config): fix config option", + "scipy": "BUG: config: fix config option", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -572,7 +572,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -602,7 +602,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -634,7 +634,7 @@ def _get_repo_from_defintion( { "conventional": "feat(feat-2): add another primary feature", "emoji": ":sparkles: (feat-2) add another primary feature", - "scipy": "ENH(feat-2): add another primary feature", + "scipy": "ENH: feat-2: add another primary feature", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -653,7 +653,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -706,7 +706,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -736,7 +736,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -766,7 +766,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], diff --git a/tests/fixtures/repos/github_flow/repo_w_default_release.py b/tests/fixtures/repos/github_flow/repo_w_default_release.py index ce8877dfe..7eddbd852 100644 --- a/tests/fixtures/repos/github_flow/repo_w_default_release.py +++ b/tests/fixtures/repos/github_flow/repo_w_default_release.py @@ -106,7 +106,7 @@ def _get_repo_from_defintion( ) pr_num_gen = (i for i in count(start=2, step=1)) - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -182,7 +182,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -326,7 +326,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -377,7 +377,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], diff --git a/tests/fixtures/repos/github_flow/repo_w_release_channels.py b/tests/fixtures/repos/github_flow/repo_w_release_channels.py index 07be6eb5a..c0670a968 100644 --- a/tests/fixtures/repos/github_flow/repo_w_release_channels.py +++ b/tests/fixtures/repos/github_flow/repo_w_release_channels.py @@ -106,7 +106,7 @@ def _get_repo_from_defintion( ) pr_num_gen = (i for i in count(start=2, step=1)) - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -188,7 +188,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -237,7 +237,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -277,7 +277,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -331,7 +331,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -381,7 +381,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -435,7 +435,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py index c7a33cc16..04f0bc27c 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py @@ -99,7 +99,7 @@ def _get_repo_from_defintion( for i in count(step=1) ) - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -179,7 +179,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -219,7 +219,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -269,7 +269,7 @@ def _get_repo_from_defintion( "\n\n", [ "API: add revolutionary feature", - "BREAKING CHANGE: this is a breaking change", + "This is a breaking change", ], ), "datetime": next(commit_timestamp_gen), @@ -290,7 +290,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -335,7 +335,7 @@ def _get_repo_from_defintion( "details": { "new_version": new_version, "max_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py index 2576ec510..d0458cb90 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py @@ -99,7 +99,7 @@ def _get_repo_from_defintion( for i in count(step=1) ) - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -179,7 +179,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -219,7 +219,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -269,7 +269,7 @@ def _get_repo_from_defintion( "\n\n", [ "API: add revolutionary feature", - "BREAKING CHANGE: this is a breaking change", + "This is a breaking change", ], ), "datetime": next(commit_timestamp_gen), @@ -290,7 +290,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -336,7 +336,7 @@ def _get_repo_from_defintion( "details": { "new_version": new_version, "max_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -377,7 +377,7 @@ def _get_repo_from_defintion( "details": { "new_version": new_version, "max_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -418,7 +418,7 @@ def _get_repo_from_defintion( "details": { "new_version": new_version, "max_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py b/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py index a2c133d21..8ac0b0674 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py @@ -93,7 +93,7 @@ def _get_repo_from_defintion( for i in count(step=1) ) - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -169,7 +169,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -209,7 +209,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -249,7 +249,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -270,7 +270,7 @@ def _get_repo_from_defintion( { "conventional": "feat(cli): add cli command", "emoji": ":sparkles:(cli) add cli command", - "scipy": "ENH(cli): add cli command", + "scipy": "ENH: cli: add cli command", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, @@ -289,7 +289,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py b/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py index 9d080ed7a..c8bfd35d1 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py @@ -95,7 +95,7 @@ def _get_repo_from_defintion( for i in count(step=1) ) - changelog_file_definitons: Sequence[RepoActionWriteChangelogsDestFile] = [ + changelog_file_definitions: Sequence[RepoActionWriteChangelogsDestFile] = [ { "path": changelog_md_file, "format": ChangelogOutputFormat.MARKDOWN, @@ -171,7 +171,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], @@ -211,7 +211,7 @@ def _get_repo_from_defintion( "action": RepoActionStep.WRITE_CHANGELOGS, "details": { "new_version": new_version, - "dest_files": changelog_file_definitons, + "dest_files": changelog_file_definitions, }, }, ], From fd3076ef9da9dcd23e91cdf73f29576806922c52 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 17:29:12 -0600 Subject: [PATCH 065/120] test(fixtures): harden test fixtures from CI environment variables --- tests/fixtures/git_repo.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/fixtures/git_repo.py b/tests/fixtures/git_repo.py index 9601f44fe..be1ee0fa0 100644 --- a/tests/fixtures/git_repo.py +++ b/tests/fixtures/git_repo.py @@ -8,7 +8,7 @@ from pathlib import Path from textwrap import dedent from time import sleep -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast from unittest import mock import pytest @@ -956,10 +956,16 @@ def _get_hvcs_client_from_repo_def( # Prevent the HVCS client from using the environment variables with mock.patch.dict(os.environ, {}, clear=True): - return hvcs_client_class( - example_git_https_url, - hvcs_domain=get_cfg_value_from_def(repo_def, "hvcs_domain"), + hvcs_client = cast( + "HvcsBase", + hvcs_client_class( + example_git_https_url, + hvcs_domain=get_cfg_value_from_def(repo_def, "hvcs_domain"), + ), ) + # Force the HVCS client to attempt to resolve the repo name (as we generally cache it) + assert hvcs_client.repo_name + return cast("Github | Gitlab | Gitea | Bitbucket", hvcs_client) return _get_hvcs_client_from_repo_def From 5310d0c700840538f27874394b9964bf09cd69b1 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 00:29:19 -0600 Subject: [PATCH 066/120] docs(commit-parsing): define limitation of revert commits with the scipy parser --- docs/commit_parsing.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/commit_parsing.rst b/docs/commit_parsing.rst index 65e523d98..16340abeb 100644 --- a/docs/commit_parsing.rst +++ b/docs/commit_parsing.rst @@ -294,6 +294,11 @@ Guidelines`_ with all different commit types. Because of this small variance, th only extends our :ref:`commit_parser-builtin-angular` parser with pre-defined scipy commit types in the default Scipy Parser Options and all other features are inherited. +**Limitations**: + +- Commits with the ``REV`` type are not currently supported. Track the implementation + of this feature in the issue `#402`_. + If no commit parser options are provided via the configuration, the parser will use PSR's built-in :py:class:`defaults `. From 86f6bd98808f22fa9b3500c03ac4c2f60bf2c2c2 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 20:02:41 -0600 Subject: [PATCH 067/120] test: refactor to use cli runner with empty default env --- tests/conftest.py | 45 ++++++ tests/e2e/cmd_changelog/test_changelog.py | 128 +++++++----------- .../test_changelog_custom_parser.py | 8 +- .../cmd_changelog/test_changelog_parsing.py | 8 +- .../test_changelog_release_notes.py | 18 ++- tests/e2e/cmd_config/test_generate_config.py | 22 ++- tests/e2e/cmd_publish/test_publish.py | 16 +-- .../git_flow/test_repo_1_channel.py | 8 +- .../git_flow/test_repo_2_channels.py | 8 +- .../git_flow/test_repo_3_channels.py | 8 +- .../git_flow/test_repo_4_channels.py | 8 +- .../github_flow/test_repo_1_channel.py | 8 +- .../github_flow/test_repo_2_channels.py | 8 +- .../trunk_based_dev/test_repo_trunk.py | 8 +- .../test_repo_trunk_dual_version_support.py | 8 +- ...runk_dual_version_support_w_prereleases.py | 8 +- .../test_repo_trunk_w_prereleases.py | 8 +- tests/e2e/cmd_version/test_version.py | 24 ++-- tests/e2e/cmd_version/test_version_build.py | 30 ++-- tests/e2e/cmd_version/test_version_bump.py | 60 ++++---- .../e2e/cmd_version/test_version_changelog.py | 25 ++-- ...est_version_changelog_custom_commit_msg.py | 11 +- .../test_version_github_actions.py | 27 ++-- tests/e2e/cmd_version/test_version_print.py | 60 ++++---- .../cmd_version/test_version_release_notes.py | 12 +- tests/e2e/cmd_version/test_version_stamp.py | 36 +++-- tests/e2e/cmd_version/test_version_strict.py | 12 +- tests/e2e/test_help.py | 21 ++- tests/e2e/test_main.py | 45 +++--- tests/fixtures/git_repo.py | 4 +- tests/util.py | 5 +- 31 files changed, 328 insertions(+), 369 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2897bbac7..16298e98b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,7 @@ from pathlib import Path from tempfile import NamedTemporaryFile from typing import TYPE_CHECKING +from unittest import mock import pytest from click.testing import CliRunner @@ -24,11 +25,36 @@ from tempfile import _TemporaryFileWrapper from typing import Any, Callable, Generator, Protocol, Sequence, TypedDict + from click.testing import Result from filelock import AcquireReturnProxy from git import Actor from tests.fixtures.git_repo import RepoActions + class RunCliFn(Protocol): + """ + Run the CLI with the provided arguments and a clean environment. + + :param argv: The arguments to pass to the CLI. + :type argv: list[str] | None + + :param env: The environment variables to set for the CLI. + :type env: dict[str, str] | None + + :param invoke_kwargs: Additional arguments to pass to the invoke method. + :type invoke_kwargs: dict[str, Any] | None + + :return: The result of the CLI invocation. + :rtype: Result + """ + + def __call__( + self, + argv: list[str] | None = None, + env: dict[str, str] | None = None, + invoke_kwargs: dict[str, Any] | None = None, + ) -> Result: ... + class MakeCommitObjFn(Protocol): def __call__(self, message: str) -> Commit: ... @@ -170,6 +196,25 @@ def cli_runner() -> CliRunner: return CliRunner(mix_stderr=False) +@pytest.fixture(scope="session") +def run_cli(clean_os_environment: dict[str, str]) -> RunCliFn: + def _run_cli( + argv: list[str] | None = None, + env: dict[str, str] | None = None, + invoke_kwargs: dict[str, Any] | None = None, + ) -> Result: + from semantic_release.cli.commands.main import main + + cli_runner = CliRunner(mix_stderr=False) + env_vars = {**clean_os_environment, **(env or {})} + + with mock.patch.dict(os.environ, env_vars, clear=True): + # run the CLI with the provided arguments + return cli_runner.invoke(main, args=(argv or []), **(invoke_kwargs or {})) + + return _run_cli + + @pytest.fixture(scope="session") def default_netrc_username() -> str: return "username" diff --git a/tests/e2e/cmd_changelog/test_changelog.py b/tests/e2e/cmd_changelog/test_changelog.py index d717df497..edc2a8c63 100644 --- a/tests/e2e/cmd_changelog/test_changelog.py +++ b/tests/e2e/cmd_changelog/test_changelog.py @@ -1,7 +1,6 @@ from __future__ import annotations import os -import sys from textwrap import dedent from typing import TYPE_CHECKING from unittest import mock @@ -13,7 +12,6 @@ import semantic_release.hvcs.github from semantic_release.changelog.context import ChangelogMode -from semantic_release.cli.commands.main import main from semantic_release.cli.config import ChangelogOutputFormat from semantic_release.hvcs.github import Github from semantic_release.version.version import Version @@ -77,9 +75,9 @@ from pathlib import Path from typing import TypedDict - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.conftest import RetrieveRuntimeContextFn from tests.fixtures.example_project import ( ExProjectDir, @@ -123,7 +121,7 @@ class Commit2SectionCommit(TypedDict): def test_changelog_noop_is_noop( repo_result: BuiltRepoResult, arg0: str | None, - cli_runner: CliRunner, + run_cli: RunCliFn, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, ): repo = repo_result["repo"] @@ -152,7 +150,7 @@ def test_changelog_noop_is_noop( ), requests_mock.Mocker(session=session) as mocker: args = [arg0, f"v{version_str}"] if version_str and arg0 else [] cli_cmd = [MAIN_PROG_NAME, "--noop", CHANGELOG_SUBCMD, *args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -227,7 +225,7 @@ def test_changelog_noop_is_noop( ) def test_changelog_content_regenerated( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_file: Path, insertion_flag: str, @@ -255,7 +253,7 @@ def test_changelog_content_regenerated( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -289,7 +287,7 @@ def test_changelog_content_regenerated_masked_initial_release( build_repo_from_definition: BuildRepoFromDefinitionFn, get_repo_definition_4_trunk_only_repo_w_tags: GetRepoDefinitionFn, example_project_dir: ExProjectDir, - cli_runner: CliRunner, + run_cli: RunCliFn, changelog_file: Path, insertion_flag: str, ): @@ -319,7 +317,7 @@ def test_changelog_content_regenerated_masked_initial_release( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -353,7 +351,7 @@ def test_changelog_content_regenerated_masked_initial_release( ) def test_changelog_update_mode_unchanged( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_file: Path, ): @@ -376,7 +374,7 @@ def test_changelog_update_mode_unchanged( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -413,7 +411,7 @@ def test_changelog_update_mode_unchanged( ) def test_changelog_update_mode_no_prev_changelog( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_file: Path, ): @@ -439,7 +437,7 @@ def test_changelog_update_mode_no_prev_changelog( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -481,7 +479,7 @@ def test_changelog_update_mode_no_prev_changelog( ) def test_changelog_update_mode_no_flag( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_file: Path, insertion_flag: str, @@ -514,7 +512,7 @@ def test_changelog_update_mode_no_flag( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -555,7 +553,7 @@ def test_changelog_update_mode_no_flag( ) def test_changelog_update_mode_no_header( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_format: ChangelogOutputFormat, changelog_file: Path, @@ -613,7 +611,7 @@ def test_changelog_update_mode_no_header( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -657,7 +655,7 @@ def test_changelog_update_mode_no_header( ) def test_changelog_update_mode_no_footer( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_format: ChangelogOutputFormat, changelog_file: Path, @@ -717,7 +715,7 @@ def test_changelog_update_mode_no_footer( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -761,7 +759,7 @@ def test_changelog_update_mode_no_footer( ) def test_changelog_update_mode_no_releases( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_file: Path, insertion_flag: str, @@ -816,7 +814,7 @@ def test_changelog_update_mode_no_releases( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -862,7 +860,7 @@ def test_changelog_update_mode_unreleased_n_released( repo_result: BuiltRepoResult, commit_type: CommitConvention, changelog_format: ChangelogOutputFormat, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, example_git_ssh_url: str, file_in_repo: str, @@ -933,7 +931,10 @@ def test_changelog_update_mode_unreleased_n_released( repo, commit_n_section[commit_type]["commit"], ) - hvcs = Github(example_git_ssh_url, hvcs_domain=EXAMPLE_HVCS_DOMAIN) + + with mock.patch.dict(os.environ, {}, clear=True): + hvcs = Github(example_git_ssh_url, hvcs_domain=EXAMPLE_HVCS_DOMAIN) + assert hvcs.repo_name # force caching of repo values (ignoring the env) unreleased_change_variants = { ChangelogOutputFormat.MARKDOWN: dedent( @@ -992,7 +993,7 @@ def test_changelog_update_mode_unreleased_n_released( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -1015,11 +1016,11 @@ def test_changelog_update_mode_unreleased_n_released( ) def test_changelog_release_tag_not_in_history( args: list[str], - cli_runner: CliRunner, + run_cli: RunCliFn, ): # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD, *args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_exit_code(2, result, cli_cmd) @@ -1035,7 +1036,7 @@ def test_changelog_release_tag_not_in_history( ("--post-to-release-tag", "v0.2.0"), # latest release ], ) -def test_changelog_post_to_release(args: list[str], cli_runner: CliRunner): +def test_changelog_post_to_release(args: list[str], run_cli: RunCliFn): # Set up a requests HTTP session so we can catch the HTTP calls and ensure they're # made @@ -1055,59 +1056,22 @@ def test_changelog_post_to_release(args: list[str], cli_runner: CliRunner): repo_name=EXAMPLE_REPO_NAME, ) - clean_os_environment = dict( - filter( - lambda k_v: k_v[1] is not None, - { - "CI": "true", - "PATH": os.getenv("PATH"), - "HOME": os.getenv("HOME"), - "VIRTUAL_ENV": os.getenv("VIRTUAL_ENV", "./.venv"), - **( - {} - if sys.platform != "win32" - else { - # Windows Required variables - "ALLUSERSAPPDATA": os.getenv("ALLUSERSAPPDATA"), - "ALLUSERSPROFILE": os.getenv("ALLUSERSPROFILE"), - "APPDATA": os.getenv("APPDATA"), - "COMMONPROGRAMFILES": os.getenv("COMMONPROGRAMFILES"), - "COMMONPROGRAMFILES(X86)": os.getenv("COMMONPROGRAMFILES(X86)"), - "DEFAULTUSERPROFILE": os.getenv("DEFAULTUSERPROFILE"), - "HOMEPATH": os.getenv("HOMEPATH"), - "PATHEXT": os.getenv("PATHEXT"), - "PROFILESFOLDER": os.getenv("PROFILESFOLDER"), - "PROGRAMFILES": os.getenv("PROGRAMFILES"), - "PROGRAMFILES(X86)": os.getenv("PROGRAMFILES(X86)"), - "SYSTEM": os.getenv("SYSTEM"), - "SYSTEM16": os.getenv("SYSTEM16"), - "SYSTEM32": os.getenv("SYSTEM32"), - "SYSTEMDRIVE": os.getenv("SYSTEMDRIVE"), - "SYSTEMROOT": os.getenv("SYSTEMROOT"), - "TEMP": os.getenv("TEMP"), - "TMP": os.getenv("TMP"), - "USERPROFILE": os.getenv("USERPROFILE"), - "USERSID": os.getenv("USERSID"), - "USERNAME": os.getenv("USERNAME"), - "WINDIR": os.getenv("WINDIR"), - } - ), - }.items(), - ) - ) - # Patch out env vars that affect changelog URLs but only get set in e.g. # Github actions with mock.patch( # Patching the specific module's reference to the build_requests_session function f"{semantic_release.hvcs.github.__name__}.{semantic_release.hvcs.github.build_requests_session.__name__}", return_value=session, - ) as build_requests_session_mock, mock.patch.dict( - os.environ, clean_os_environment, clear=True - ): + ) as build_requests_session_mock: # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD, *args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli( + cli_cmd[1:], + env={ + "CI": "true", + "VIRTUAL_ENV": os.getenv("VIRTUAL_ENV", "./.venv"), + }, + ) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -1127,7 +1091,7 @@ def test_custom_release_notes_template( use_release_notes_template: UseReleaseNotesTemplateFn, retrieve_runtime_context: RetrieveRuntimeContextFn, post_mocker: Mocker, - cli_runner: CliRunner, + run_cli: RunCliFn, ) -> None: """Verify the template `.release_notes.md.j2` from `template_dir` is used.""" expected_call_count = 1 @@ -1157,7 +1121,7 @@ def test_custom_release_notes_template( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD, "--post-to-release-tag", tag] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Assert assert_successful_exit_code(result, cli_cmd) @@ -1174,7 +1138,7 @@ def test_changelog_default_on_empty_template_dir( changelog_template_dir: Path, example_project_template_dir: Path, update_pyproject_toml: UpdatePyprojectTomlFn, - cli_runner: CliRunner, + run_cli: RunCliFn, ): # Setup: Make sure default changelog doesn't already exist example_changelog_md.unlink(missing_ok=True) @@ -1190,7 +1154,7 @@ def test_changelog_default_on_empty_template_dir( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -1205,7 +1169,7 @@ def test_changelog_default_on_incorrect_config_template_file( changelog_template_dir: Path, example_project_template_dir: Path, update_pyproject_toml: UpdatePyprojectTomlFn, - cli_runner: CliRunner, + run_cli: RunCliFn, ): # Setup: Make sure default changelog doesn't already exist example_changelog_md.unlink(missing_ok=True) @@ -1222,7 +1186,7 @@ def test_changelog_default_on_incorrect_config_template_file( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -1236,7 +1200,7 @@ def test_changelog_default_on_incorrect_config_template_file( def test_changelog_prevent_malicious_path_traversal_file( update_pyproject_toml: UpdatePyprojectTomlFn, bad_changelog_file_str: str, - cli_runner: CliRunner, + run_cli: RunCliFn, ): # Setup: A malicious path traversal filepath outside of the repository update_pyproject_toml( @@ -1246,7 +1210,7 @@ def test_changelog_prevent_malicious_path_traversal_file( # Act cli_cmd = [MAIN_PROG_NAME, "--noop", CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_exit_code(1, result, cli_cmd) @@ -1261,7 +1225,7 @@ def test_changelog_prevent_malicious_path_traversal_file( def test_changelog_prevent_external_path_traversal_dir( update_pyproject_toml: UpdatePyprojectTomlFn, template_dir_path: str, - cli_runner: CliRunner, + run_cli: RunCliFn, ): # Setup: A malicious path traversal filepath outside of the repository update_pyproject_toml( @@ -1271,7 +1235,7 @@ def test_changelog_prevent_external_path_traversal_dir( # Act cli_cmd = [MAIN_PROG_NAME, "--noop", CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_exit_code(1, result, cli_cmd) diff --git a/tests/e2e/cmd_changelog/test_changelog_custom_parser.py b/tests/e2e/cmd_changelog/test_changelog_custom_parser.py index 3c6d88f7a..0173cb49d 100644 --- a/tests/e2e/cmd_changelog/test_changelog_custom_parser.py +++ b/tests/e2e/cmd_changelog/test_changelog_custom_parser.py @@ -7,7 +7,6 @@ from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture from semantic_release.changelog.context import ChangelogMode -from semantic_release.cli.commands.main import main from tests.const import CHANGELOG_SUBCMD, MAIN_PROG_NAME from tests.fixtures.repos import repo_w_no_tags_conventional_commits @@ -19,8 +18,7 @@ if TYPE_CHECKING: from pathlib import Path - from click.testing import CliRunner - + from tests.conftest import RunCliFn from tests.fixtures.example_project import UpdatePyprojectTomlFn, UseCustomParserFn from tests.fixtures.git_repo import BuiltRepoResult, GetCommitDefFn @@ -30,7 +28,7 @@ ) def test_changelog_custom_parser_remove_from_changelog( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, use_custom_parser: UseCustomParserFn, get_commit_def_of_conventional_commit: GetCommitDefFn, @@ -70,7 +68,7 @@ def test_changelog_custom_parser_remove_from_changelog( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Take measurement after action actual_content = changelog_md_file.read_text() diff --git a/tests/e2e/cmd_changelog/test_changelog_parsing.py b/tests/e2e/cmd_changelog/test_changelog_parsing.py index 40b6923cc..4c5c8f2e6 100644 --- a/tests/e2e/cmd_changelog/test_changelog_parsing.py +++ b/tests/e2e/cmd_changelog/test_changelog_parsing.py @@ -10,7 +10,6 @@ from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture from semantic_release.changelog.context import ChangelogMode -from semantic_release.cli.commands.main import main from semantic_release.cli.const import JINJA2_EXTENSION from tests.const import CHANGELOG_SUBCMD, MAIN_PROG_NAME @@ -29,8 +28,7 @@ from tests.util import assert_successful_exit_code if TYPE_CHECKING: - from click.testing import CliRunner - + from tests.conftest import RunCliFn from tests.fixtures.example_project import UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuiltRepoResult @@ -68,7 +66,7 @@ ], ) def test_changelog_parsing_ignore_merge_commits( - cli_runner: CliRunner, + run_cli: RunCliFn, repo_result: BuiltRepoResult, update_pyproject_toml: UpdatePyprojectTomlFn, example_project_template_dir: Path, @@ -131,7 +129,7 @@ def test_changelog_parsing_ignore_merge_commits( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) diff --git a/tests/e2e/cmd_changelog/test_changelog_release_notes.py b/tests/e2e/cmd_changelog/test_changelog_release_notes.py index e585f8b63..ca6d26563 100644 --- a/tests/e2e/cmd_changelog/test_changelog_release_notes.py +++ b/tests/e2e/cmd_changelog/test_changelog_release_notes.py @@ -6,7 +6,6 @@ import pytest from pytest_lazy_fixtures import lf as lazy_fixture -from semantic_release.cli.commands.main import main from semantic_release.version.version import Version from tests.const import CHANGELOG_SUBCMD, EXAMPLE_PROJECT_LICENSE, MAIN_PROG_NAME @@ -20,10 +19,9 @@ from tests.util import assert_successful_exit_code if TYPE_CHECKING: - from click.testing import CliRunner from requests_mock import Mocker - from tests.conftest import GetStableDateNowFn + from tests.conftest import GetStableDateNowFn, RunCliFn from tests.fixtures.example_project import UpdatePyprojectTomlFn from tests.fixtures.git_repo import ( BuiltRepoResult, @@ -49,7 +47,7 @@ def test_changelog_latest_release_notes( get_cfg_value_from_def: GetCfgValueFromDefFn, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, get_hvcs_client_from_repo_def: GetHvcsClientFromRepoDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, post_mocker: Mocker, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, generate_default_release_notes_from_def: GenerateDefaultReleaseNotesFromDefFn, @@ -77,7 +75,7 @@ def test_changelog_latest_release_notes( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD, "--post-to-release-tag", release_tag] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -122,7 +120,7 @@ def test_changelog_previous_release_notes( get_cfg_value_from_def: GetCfgValueFromDefFn, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, get_hvcs_client_from_repo_def: GetHvcsClientFromRepoDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, post_mocker: Mocker, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, generate_default_release_notes_from_def: GenerateDefaultReleaseNotesFromDefFn, @@ -157,7 +155,7 @@ def test_changelog_previous_release_notes( # Act cli_cmd = [MAIN_PROG_NAME, CHANGELOG_SUBCMD, "--post-to-release-tag", release_tag] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -213,7 +211,7 @@ def test_changelog_release_notes_license_change( get_cfg_value_from_def: GetCfgValueFromDefFn, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, get_hvcs_client_from_repo_def: GetHvcsClientFromRepoDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, post_mocker: Mocker, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, generate_default_release_notes_from_def: GenerateDefaultReleaseNotesFromDefFn, @@ -296,7 +294,7 @@ def test_changelog_release_notes_license_change( "--post-to-release-tag", latest_release_tag, ] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -316,7 +314,7 @@ def test_changelog_release_notes_license_change( "--post-to-release-tag", prev_release_tag, ] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) diff --git a/tests/e2e/cmd_config/test_generate_config.py b/tests/e2e/cmd_config/test_generate_config.py index 3d49a3136..4a21f0be7 100644 --- a/tests/e2e/cmd_config/test_generate_config.py +++ b/tests/e2e/cmd_config/test_generate_config.py @@ -6,7 +6,6 @@ import pytest import tomlkit -from semantic_release.cli.commands.main import main from semantic_release.cli.config import RawConfig from tests.const import GENERATE_CONFIG_SUBCMD, MAIN_PROG_NAME, VERSION_SUBCMD @@ -17,8 +16,7 @@ from pathlib import Path from typing import Any - from click.testing import CliRunner - + from tests.conftest import RunCliFn from tests.fixtures.example_project import ExProjectDir @@ -30,7 +28,7 @@ def raw_config_dict() -> dict[str, Any]: @pytest.mark.parametrize("args", [(), ("--format", "toml"), ("--format", "TOML")]) @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_generate_config_toml( - cli_runner: CliRunner, + run_cli: RunCliFn, args: tuple[str], raw_config_dict: dict[str, Any], example_project_dir: ExProjectDir, @@ -42,7 +40,7 @@ def test_generate_config_toml( # Act: Print the generated configuration to stdout cli_cmd = [MAIN_PROG_NAME, GENERATE_CONFIG_SUBCMD, *args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate: Check that the command ran successfully and that the output matches the expected configuration assert_successful_exit_code(result, cli_cmd) @@ -62,7 +60,7 @@ def test_generate_config_toml( VERSION_SUBCMD, "--print", ] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate: Check that the version command in noop mode ran successfully # which means PSR loaded the configuration successfully @@ -72,7 +70,7 @@ def test_generate_config_toml( @pytest.mark.parametrize("args", [("--format", "json"), ("--format", "JSON")]) @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_generate_config_json( - cli_runner: CliRunner, + run_cli: RunCliFn, args: tuple[str], raw_config_dict: dict[str, Any], example_project_dir: ExProjectDir, @@ -84,7 +82,7 @@ def test_generate_config_json( # Act: Print the generated configuration to stdout cli_cmd = [MAIN_PROG_NAME, GENERATE_CONFIG_SUBCMD, *args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate: Check that the command ran successfully and that the output matches the expected configuration assert_successful_exit_code(result, cli_cmd) @@ -104,7 +102,7 @@ def test_generate_config_json( VERSION_SUBCMD, "--print", ] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate: Check that the version command in noop mode ran successfully # which means PSR loaded the configuration successfully @@ -113,7 +111,7 @@ def test_generate_config_json( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_generate_config_pyproject_toml( - cli_runner: CliRunner, + run_cli: RunCliFn, raw_config_dict: dict[str, Any], example_pyproject_toml: Path, ): @@ -135,7 +133,7 @@ def test_generate_config_pyproject_toml( "toml", "--pyproject", ] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate: Check that the command ran successfully and that the output matches the expected configuration assert_successful_exit_code(result, cli_cmd) @@ -154,7 +152,7 @@ def test_generate_config_pyproject_toml( # Act: Validate that the generated config is a valid configuration for PSR cli_cmd = [MAIN_PROG_NAME, "--noop", "--strict", VERSION_SUBCMD, "--print"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate: Check that the version command in noop mode ran successfully # which means PSR loaded the configuration successfully diff --git a/tests/e2e/cmd_publish/test_publish.py b/tests/e2e/cmd_publish/test_publish.py index ba5307fec..3b4fca2bf 100644 --- a/tests/e2e/cmd_publish/test_publish.py +++ b/tests/e2e/cmd_publish/test_publish.py @@ -6,7 +6,6 @@ import pytest from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture -from semantic_release.cli.commands.main import main from semantic_release.hvcs import Github from tests.const import MAIN_PROG_NAME, PUBLISH_SUBCMD @@ -16,8 +15,7 @@ if TYPE_CHECKING: from typing import Sequence - from click.testing import CliRunner - + from tests.conftest import RunCliFn from tests.fixtures.git_repo import BuiltRepoResult, GetVersionsFromRepoBuildDefFn @@ -27,7 +25,7 @@ ) def test_publish_latest_uses_latest_tag( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, cmd_args: Sequence[str], get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, ): @@ -41,7 +39,7 @@ def test_publish_latest_uses_latest_tag( cli_cmd = [MAIN_PROG_NAME, PUBLISH_SUBCMD, *cmd_args] # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -53,7 +51,7 @@ def test_publish_latest_uses_latest_tag( ) def test_publish_to_tag_uses_tag( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, ): # Testing a non-latest tag to distinguish from test_publish_latest_uses_latest_tag() @@ -64,7 +62,7 @@ def test_publish_to_tag_uses_tag( cli_cmd = [MAIN_PROG_NAME, PUBLISH_SUBCMD, "--tag", previous_tag] # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -74,14 +72,14 @@ def test_publish_to_tag_uses_tag( @pytest.mark.usefixtures(repo_w_trunk_only_conventional_commits.__name__) -def test_publish_fails_on_nonexistant_tag(cli_runner: CliRunner): +def test_publish_fails_on_nonexistant_tag(run_cli: RunCliFn): non_existant_tag = "nonexistant-tag" with mock.patch.object(Github, Github.upload_dists.__name__) as mocked_upload_dists: cli_cmd = [MAIN_PROG_NAME, PUBLISH_SUBCMD, "--tag", non_existant_tag] # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_exit_code(1, result, cli_cmd) diff --git a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_1_channel.py b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_1_channel.py index e12ca31e6..979a1ad8e 100644 --- a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_1_channel.py +++ b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_1_channel.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -24,9 +22,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -55,7 +53,7 @@ ) def test_gitflow_repo_rebuild_1_channel( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_git_flow_repo_w_1_release_channels: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -145,7 +143,7 @@ def test_gitflow_repo_rebuild_1_channel( else [] ) cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_2_channels.py b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_2_channels.py index 035f679bd..5ffc6bef4 100644 --- a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_2_channels.py +++ b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_2_channels.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -24,9 +22,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -55,7 +53,7 @@ ) def test_gitflow_repo_rebuild_2_channels( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_git_flow_repo_w_2_release_channels: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -145,7 +143,7 @@ def test_gitflow_repo_rebuild_2_channels( else [] ) cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_3_channels.py b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_3_channels.py index 825b7f7c3..c9bd6ccc5 100644 --- a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_3_channels.py +++ b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_3_channels.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -25,9 +23,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -57,7 +55,7 @@ ) def test_gitflow_repo_rebuild_3_channels( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_git_flow_repo_w_3_release_channels: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -147,7 +145,7 @@ def test_gitflow_repo_rebuild_3_channels( else [] ) cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_4_channels.py b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_4_channels.py index e1cadb5a6..e031d86d4 100644 --- a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_4_channels.py +++ b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_4_channels.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -24,9 +22,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -55,7 +53,7 @@ ) def test_gitflow_repo_rebuild_4_channels( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_git_flow_repo_w_4_release_channels: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -145,7 +143,7 @@ def test_gitflow_repo_rebuild_4_channels( else [] ) cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/bump_version/github_flow/test_repo_1_channel.py b/tests/e2e/cmd_version/bump_version/github_flow/test_repo_1_channel.py index e836716d6..fe166f540 100644 --- a/tests/e2e/cmd_version/bump_version/github_flow/test_repo_1_channel.py +++ b/tests/e2e/cmd_version/bump_version/github_flow/test_repo_1_channel.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -24,9 +22,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -55,7 +53,7 @@ ) def test_githubflow_repo_rebuild_1_channel( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_repo_w_github_flow_w_default_release_channel: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -145,7 +143,7 @@ def test_githubflow_repo_rebuild_1_channel( else [] ) cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/bump_version/github_flow/test_repo_2_channels.py b/tests/e2e/cmd_version/bump_version/github_flow/test_repo_2_channels.py index 03054ac6a..3f944fd3b 100644 --- a/tests/e2e/cmd_version/bump_version/github_flow/test_repo_2_channels.py +++ b/tests/e2e/cmd_version/bump_version/github_flow/test_repo_2_channels.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -24,9 +22,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -55,7 +53,7 @@ ) def test_githubflow_repo_rebuild_2_channels( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_repo_w_github_flow_w_feature_release_channel: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -145,7 +143,7 @@ def test_githubflow_repo_rebuild_2_channels( else [] ) cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk.py b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk.py index fac01bdff..e091b5d1e 100644 --- a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk.py +++ b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -24,9 +22,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -57,7 +55,7 @@ ) def test_trunk_repo_rebuild_only_official_releases( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_trunk_only_repo_w_tags: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -147,7 +145,7 @@ def test_trunk_repo_rebuild_only_official_releases( else [] ) cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support.py b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support.py index 6c15f2bd8..f2f8c0ccf 100644 --- a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support.py +++ b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( DEFAULT_BRANCH_NAME, MAIN_PROG_NAME, @@ -25,9 +23,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -56,7 +54,7 @@ ) def test_trunk_repo_rebuild_dual_version_spt_official_releases_only( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_trunk_only_repo_w_dual_version_support: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -151,7 +149,7 @@ def test_trunk_repo_rebuild_dual_version_spt_official_releases_only( else [] ) cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support_w_prereleases.py b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support_w_prereleases.py index 74d5f361f..bd00935f7 100644 --- a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support_w_prereleases.py +++ b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support_w_prereleases.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( DEFAULT_BRANCH_NAME, MAIN_PROG_NAME, @@ -25,9 +23,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -56,7 +54,7 @@ ) def test_trunk_repo_rebuild_dual_version_spt_w_official_n_prereleases( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_trunk_only_repo_w_dual_version_spt_w_prereleases: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -171,7 +169,7 @@ def test_trunk_repo_rebuild_dual_version_spt_w_official_n_prereleases( *build_metadata_args, *prerelease_args, ] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_w_prereleases.py b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_w_prereleases.py index 9d1171e59..0d0aff235 100644 --- a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_w_prereleases.py +++ b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_w_prereleases.py @@ -7,8 +7,6 @@ from flatdict import FlatDict from freezegun import freeze_time -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -24,9 +22,9 @@ from pathlib import Path from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir @@ -55,7 +53,7 @@ ) def test_trunk_repo_rebuild_w_prereleases( repo_fixture_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, build_trunk_only_repo_w_prerelease_tags: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -165,7 +163,7 @@ def test_trunk_repo_rebuild_w_prereleases( *build_metadata_args, *prerelease_args, ] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message diff --git a/tests/e2e/cmd_version/test_version.py b/tests/e2e/cmd_version/test_version.py index 9586073c3..f892c3cf6 100644 --- a/tests/e2e/cmd_version/test_version.py +++ b/tests/e2e/cmd_version/test_version.py @@ -7,8 +7,6 @@ import pytest from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -22,10 +20,10 @@ if TYPE_CHECKING: from unittest.mock import MagicMock - from click.testing import CliRunner from git import Repo from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.fixtures.example_project import GetWheelFileFn, UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuiltRepoResult, GetVersionsFromRepoBuildDefFn @@ -40,7 +38,7 @@ def test_version_noop_is_noop( repo_result: BuiltRepoResult, next_release_version: str, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, get_wheel_file: GetWheelFileFn, @@ -57,7 +55,7 @@ def test_version_noop_is_noop( # Act cli_cmd = [MAIN_PROG_NAME, "--noop", VERSION_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -88,7 +86,7 @@ def test_version_noop_is_noop( ) def test_version_no_git_verify( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: MagicMock, post_mocker: Mocker, @@ -127,7 +125,7 @@ def test_version_no_git_verify( # Execute cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--patch"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Take measurement after the command head_after = repo.head.commit @@ -148,7 +146,7 @@ def test_version_no_git_verify( ) def test_version_on_nonrelease_branch( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -170,7 +168,7 @@ def test_version_on_nonrelease_branch( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate (expected -> actual) assert_successful_exit_code(result, cli_cmd) @@ -193,7 +191,7 @@ def test_version_on_nonrelease_branch( def test_version_on_last_release( repo_result: BuiltRepoResult, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -219,7 +217,7 @@ def test_version_on_last_release( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -244,7 +242,7 @@ def test_version_on_last_release( ) def test_version_only_tag_push( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ) -> None: @@ -265,7 +263,7 @@ def test_version_only_tag_push( "--no-commit", "--tag", ] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # capture values after the command tag_after = repo.tags[-1].name diff --git a/tests/e2e/cmd_version/test_version_build.py b/tests/e2e/cmd_version/test_version_build.py index e2b42045f..1145ce627 100644 --- a/tests/e2e/cmd_version/test_version_build.py +++ b/tests/e2e/cmd_version/test_version_build.py @@ -13,15 +13,12 @@ from flatdict import FlatDict from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture -from semantic_release.cli.commands.main import main - from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD from tests.fixtures.repos import repo_w_trunk_only_conventional_commits from tests.util import assert_successful_exit_code, get_func_qual_name if TYPE_CHECKING: - from click.testing import CliRunner - + from tests.conftest import RunCliFn from tests.fixtures.example_project import GetWheelFileFn, UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuiltRepoResult @@ -67,7 +64,7 @@ def test_version_runs_build_command( repo_result: BuiltRepoResult, cli_args: list[str], next_release_version: str, - cli_runner: CliRunner, + run_cli: RunCliFn, shell: str, get_wheel_file: GetWheelFileFn, example_pyproject_toml: Path, @@ -100,10 +97,10 @@ def test_version_runs_build_command( wraps=subprocess.run, ) as patched_subprocess_run, mock.patch( get_func_qual_name(shellingham.detect_shell), return_value=(shell, shell) - ), mock.patch.dict(os.environ, patched_os_environment, clear=True): + ): # ACT: run & force a new version that will trigger the build command cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *cli_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:], env=patched_os_environment) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -147,7 +144,7 @@ def test_version_runs_build_command_windows( repo_result: BuiltRepoResult, cli_args: list[str], next_release_version: str, - cli_runner: CliRunner, + run_cli: RunCliFn, shell: str, get_wheel_file: GetWheelFileFn, example_pyproject_toml: Path, @@ -218,10 +215,10 @@ def test_version_runs_build_command_windows( wraps=subprocess.run, ) as patched_subprocess_run, mock.patch( get_func_qual_name(shellingham.detect_shell), return_value=(shell, shell) - ), mock.patch.dict(os.environ, patched_os_environment, clear=True): + ): # ACT: run & force a new version that will trigger the build command cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *cli_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:], env=patched_os_environment) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -288,12 +285,14 @@ def test_version_runs_build_command_w_user_env( repo_result: BuiltRepoResult, cli_args: list[str], next_release_version: str, - cli_runner: CliRunner, + run_cli: RunCliFn, example_pyproject_toml: Path, update_pyproject_toml: UpdatePyprojectTomlFn, + clean_os_environment: dict[str, str], ): # Setup patched_os_environment = { + **clean_os_environment, "CI": "true", "PATH": os.getenv("PATH", ""), "HOME": "/home/username", @@ -337,7 +336,7 @@ def test_version_runs_build_command_w_user_env( ) as patched_subprocess_run, mock.patch( get_func_qual_name(shellingham.detect_shell), return_value=("bash", "/usr/bin/bash"), - ), mock.patch.dict(os.environ, patched_os_environment, clear=True): + ): cli_cmd = [ MAIN_PROG_NAME, VERSION_SUBCMD, @@ -349,7 +348,7 @@ def test_version_runs_build_command_w_user_env( ] # ACT: run & force a new version that will trigger the build command - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:], env=patched_os_environment) # Evaluate # [1] Make sure it did not error internally @@ -360,6 +359,7 @@ def test_version_runs_build_command_w_user_env( ["bash", "-c", build_command], check=True, env={ + **clean_os_environment, "NEW_VERSION": next_release_version, # injected into environment "CI": patched_os_environment["CI"], "BITBUCKET_CI": "true", # Converted @@ -384,7 +384,7 @@ def test_version_runs_build_command_w_user_env( @pytest.mark.usefixtures(repo_w_trunk_only_conventional_commits.__name__) def test_version_skips_build_command_with_skip_build( - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: mock.MagicMock, post_mocker: mock.Mock, ): @@ -395,7 +395,7 @@ def test_version_skips_build_command_with_skip_build( return_value=subprocess.CompletedProcess(args=(), returncode=0), ) as patched_subprocess_run: # Act: force a new version - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) diff --git a/tests/e2e/cmd_version/test_version_bump.py b/tests/e2e/cmd_version/test_version_bump.py index 245c05505..1faa10cb2 100644 --- a/tests/e2e/cmd_version/test_version_bump.py +++ b/tests/e2e/cmd_version/test_version_bump.py @@ -11,7 +11,6 @@ # Limitation in pytest-lazy-fixture - see https://stackoverflow.com/a/69884019 from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture -from semantic_release.cli.commands.main import main from semantic_release.commit_parser.conventional import ConventionalCommitParser from semantic_release.commit_parser.emoji import EmojiCommitParser from semantic_release.commit_parser.scipy import ScipyCommitParser @@ -58,10 +57,9 @@ if TYPE_CHECKING: from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker - from tests.conftest import GetStableDateNowFn + from tests.conftest import GetStableDateNowFn, RunCliFn from tests.fixtures.example_project import ExProjectDir, UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuiltRepoResult @@ -315,7 +313,7 @@ def test_version_force_level( next_release_version: str, example_project_dir: ExProjectDir, example_pyproject_toml: Path, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -347,7 +345,7 @@ def test_version_force_level( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *cli_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -515,7 +513,7 @@ def test_version_next_greater_than_version_one_conventional( prerelease_token: str, next_release_version: str, branch_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, mocked_git_push: MagicMock, post_mocker: Mocker, @@ -554,7 +552,7 @@ def test_version_next_greater_than_version_one_conventional( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -654,7 +652,7 @@ def test_version_next_greater_than_version_one_no_bump_conventional( prerelease_token: str, next_release_version: str, branch_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, mocked_git_push: MagicMock, post_mocker: Mocker, @@ -693,7 +691,7 @@ def test_version_next_greater_than_version_one_no_bump_conventional( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -814,7 +812,7 @@ def test_version_next_greater_than_version_one_emoji( prerelease_token: str, next_release_version: str, branch_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, mocked_git_push: MagicMock, post_mocker: Mocker, @@ -853,7 +851,7 @@ def test_version_next_greater_than_version_one_emoji( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -953,7 +951,7 @@ def test_version_next_greater_than_version_one_no_bump_emoji( prerelease_token: str, next_release_version: str, branch_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, mocked_git_push: MagicMock, post_mocker: Mocker, @@ -992,7 +990,7 @@ def test_version_next_greater_than_version_one_no_bump_emoji( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -1113,7 +1111,7 @@ def test_version_next_greater_than_version_one_scipy( prerelease_token: str, next_release_version: str, branch_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, mocked_git_push: MagicMock, post_mocker: Mocker, @@ -1152,7 +1150,7 @@ def test_version_next_greater_than_version_one_scipy( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -1252,7 +1250,7 @@ def test_version_next_greater_than_version_one_no_bump_scipy( prerelease_token: str, next_release_version: str, branch_name: str, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, mocked_git_push: MagicMock, post_mocker: Mocker, @@ -1291,7 +1289,7 @@ def test_version_next_greater_than_version_one_no_bump_scipy( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -1592,7 +1590,7 @@ def test_version_next_w_zero_dot_versions_conventional( branch_name: str, major_on_zero: bool, allow_zero_version: bool, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: MagicMock, @@ -1638,7 +1636,7 @@ def test_version_next_w_zero_dot_versions_conventional( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -1746,7 +1744,7 @@ def test_version_next_w_zero_dot_versions_no_bump_conventional( branch_name: str, major_on_zero: bool, allow_zero_version: bool, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: MagicMock, @@ -1792,7 +1790,7 @@ def test_version_next_w_zero_dot_versions_no_bump_conventional( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -2072,7 +2070,7 @@ def test_version_next_w_zero_dot_versions_emoji( branch_name: str, major_on_zero: bool, allow_zero_version: bool, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: MagicMock, @@ -2118,7 +2116,7 @@ def test_version_next_w_zero_dot_versions_emoji( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -2226,7 +2224,7 @@ def test_version_next_w_zero_dot_versions_no_bump_emoji( branch_name: str, major_on_zero: bool, allow_zero_version: bool, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: MagicMock, @@ -2272,7 +2270,7 @@ def test_version_next_w_zero_dot_versions_no_bump_emoji( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -2552,7 +2550,7 @@ def test_version_next_w_zero_dot_versions_scipy( branch_name: str, major_on_zero: bool, allow_zero_version: bool, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: MagicMock, @@ -2598,7 +2596,7 @@ def test_version_next_w_zero_dot_versions_scipy( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -2706,7 +2704,7 @@ def test_version_next_w_zero_dot_versions_no_bump_scipy( branch_name: str, major_on_zero: bool, allow_zero_version: bool, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: MagicMock, @@ -2752,7 +2750,7 @@ def test_version_next_w_zero_dot_versions_no_bump_scipy( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -3095,7 +3093,7 @@ def test_version_next_w_zero_dot_versions_minimums( branch_name: str, major_on_zero: bool, allow_zero_version: bool, - cli_runner: CliRunner, + run_cli: RunCliFn, file_in_repo: str, update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: MagicMock, @@ -3142,7 +3140,7 @@ def test_version_next_w_zero_dot_versions_minimums( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, *prerelease_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit diff --git a/tests/e2e/cmd_version/test_version_changelog.py b/tests/e2e/cmd_version/test_version_changelog.py index 19a3bb3ba..212e6110e 100644 --- a/tests/e2e/cmd_version/test_version_changelog.py +++ b/tests/e2e/cmd_version/test_version_changelog.py @@ -9,7 +9,6 @@ from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture from semantic_release.changelog.context import ChangelogMode -from semantic_release.cli.commands.main import main from semantic_release.cli.config import ChangelogOutputFormat from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD @@ -48,9 +47,7 @@ if TYPE_CHECKING: from pathlib import Path - from click.testing import CliRunner - - from tests.conftest import FormatDateStrFn, GetStableDateNowFn + from tests.conftest import FormatDateStrFn, GetStableDateNowFn, RunCliFn from tests.fixtures.example_project import UpdatePyprojectTomlFn from tests.fixtures.git_repo import ( BuiltRepoResult, @@ -176,7 +173,7 @@ def test_version_updates_changelog_w_new_version( get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, tag_format: str, update_pyproject_toml: UpdatePyprojectTomlFn, - cli_runner: CliRunner, + run_cli: RunCliFn, changelog_file: Path, insertion_flag: str, cache: pytest.Cache, @@ -255,7 +252,7 @@ def test_version_updates_changelog_w_new_version( with freeze_time(now_datetime.astimezone(timezone.utc)): cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-push", "--changelog"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Capture the new changelog content (os aware because of expected content) with changelog_file.open(newline=os.linesep) as rfd: @@ -307,7 +304,7 @@ def test_version_updates_changelog_wo_prev_releases( repo_result: BuiltRepoResult, cache_key: str, cache: pytest.Cache, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_format: ChangelogOutputFormat, changelog_file: Path, @@ -408,7 +405,7 @@ def test_version_updates_changelog_wo_prev_releases( # Act with freeze_time(now_datetime.astimezone(timezone.utc)): cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-push", "--changelog"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -532,7 +529,7 @@ def test_version_initializes_changelog_in_update_mode_w_no_prev_changelog( cache_key: str, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, tag_format: str, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_file: Path, cache: pytest.Cache, @@ -581,7 +578,7 @@ def test_version_initializes_changelog_in_update_mode_w_no_prev_changelog( # Act with freeze_time(now_datetime.astimezone(timezone.utc)): cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-push", "--changelog"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -611,7 +608,7 @@ def test_version_initializes_changelog_in_update_mode_w_no_prev_changelog( @pytest.mark.usefixtures(repo_w_trunk_only_conventional_commits.__name__) def test_version_maintains_changelog_in_update_mode_w_no_flag( changelog_file: Path, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, insertion_flag: str, ): @@ -641,7 +638,7 @@ def test_version_maintains_changelog_in_update_mode_w_no_flag( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-push", "--changelog"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -687,7 +684,7 @@ def test_version_updates_changelog_w_new_version_n_filtered_commit( commit_type: CommitConvention, tag_format: str, update_pyproject_toml: UpdatePyprojectTomlFn, - cli_runner: CliRunner, + run_cli: RunCliFn, changelog_file: Path, stable_now_date: GetStableDateNowFn, get_commits_from_repo_build_def: GetCommitsFromRepoBuildDefFn, @@ -740,7 +737,7 @@ def test_version_updates_changelog_w_new_version_n_filtered_commit( # Act with freeze_time(now_datetime.astimezone(timezone.utc)): cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-push", "--changelog"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Capture the new changelog content (os aware because of expected content) actual_content = changelog_file.read_text() diff --git a/tests/e2e/cmd_version/test_version_changelog_custom_commit_msg.py b/tests/e2e/cmd_version/test_version_changelog_custom_commit_msg.py index d7ee1d08f..180e7c0f4 100644 --- a/tests/e2e/cmd_version/test_version_changelog_custom_commit_msg.py +++ b/tests/e2e/cmd_version/test_version_changelog_custom_commit_msg.py @@ -10,7 +10,6 @@ from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture from semantic_release.changelog.context import ChangelogMode -from semantic_release.cli.commands.main import main from tests.const import ( MAIN_PROG_NAME, @@ -35,9 +34,7 @@ from pathlib import Path from typing import TypedDict - from click.testing import CliRunner - - from tests.conftest import GetStableDateNowFn + from tests.conftest import GetStableDateNowFn, RunCliFn from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import UpdatePyprojectTomlFn from tests.fixtures.git_repo import ( @@ -120,7 +117,7 @@ def test_version_changelog_content_custom_commit_message_excluded_automatically( get_cfg_value_from_def: GetCfgValueFromDefFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, build_repo_from_definition: BuildRepoFromDefinitionFn, - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, changelog_file: Path, changelog_mode: ChangelogMode, @@ -192,7 +189,7 @@ def test_version_changelog_content_custom_commit_message_excluded_automatically( # Act: make the first release again with freeze_time(now_datetime.astimezone(timezone.utc)): - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) assert_successful_exit_code(result, cli_cmd) # Act: apply commits for change of version @@ -206,7 +203,7 @@ def test_version_changelog_content_custom_commit_message_excluded_automatically( # Act: make the second release again with freeze_time(now_datetime.astimezone(timezone.utc) + timedelta(minutes=1)): - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) actual_content = get_sanitized_changelog_content( repo_dir=example_project_dir, diff --git a/tests/e2e/cmd_version/test_version_github_actions.py b/tests/e2e/cmd_version/test_version_github_actions.py index c79e34b15..53917e706 100644 --- a/tests/e2e/cmd_version/test_version_github_actions.py +++ b/tests/e2e/cmd_version/test_version_github_actions.py @@ -4,8 +4,6 @@ import pytest -from semantic_release.cli.commands.main import main - from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD from tests.fixtures.repos import ( repo_w_git_flow_w_alpha_prereleases_n_conventional_commits, @@ -13,26 +11,30 @@ from tests.util import actions_output_to_dict, assert_successful_exit_code if TYPE_CHECKING: - from pathlib import Path - - from click.testing import CliRunner + from tests.conftest import RunCliFn + from tests.fixtures.example_project import ExProjectDir @pytest.mark.usefixtures( repo_w_git_flow_w_alpha_prereleases_n_conventional_commits.__name__ ) def test_version_writes_github_actions_output( - cli_runner: CliRunner, - monkeypatch: pytest.MonkeyPatch, - tmp_path: Path, + run_cli: RunCliFn, + example_project_dir: ExProjectDir, ): - mock_output_file = tmp_path / "action.out" - monkeypatch.setenv("GITHUB_OUTPUT", str(mock_output_file.resolve())) + mock_output_file = example_project_dir / "action.out" + # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--patch", "--no-push"] + result = run_cli( + cli_cmd[1:], env={"GITHUB_OUTPUT": str(mock_output_file.resolve())} + ) + assert_successful_exit_code(result, cli_cmd) - # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + if not mock_output_file.exists(): + pytest.fail( + f"Expected output file {mock_output_file} to be created, but it does not exist." + ) # Extract the output action_outputs = actions_output_to_dict( @@ -40,7 +42,6 @@ def test_version_writes_github_actions_output( ) # Evaluate - assert_successful_exit_code(result, cli_cmd) assert "released" in action_outputs assert action_outputs["released"] == "true" assert "version" in action_outputs diff --git a/tests/e2e/cmd_version/test_version_print.py b/tests/e2e/cmd_version/test_version_print.py index a259036cb..b3afc2fc3 100644 --- a/tests/e2e/cmd_version/test_version_print.py +++ b/tests/e2e/cmd_version/test_version_print.py @@ -5,8 +5,6 @@ import pytest from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture -from semantic_release.cli.commands.main import main - from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -31,9 +29,9 @@ if TYPE_CHECKING: from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.fixtures.git_repo import ( BuiltRepoResult, GetCfgValueFromDefFn, @@ -96,7 +94,7 @@ def test_version_print_next_version( force_args: list[str], next_release_version: str, file_in_repo: str, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -128,7 +126,7 @@ def test_version_print_next_version( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print", *force_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -262,7 +260,7 @@ def test_version_print_tag_prints_next_tag( next_release_version: str, get_cfg_value_from_def: GetCfgValueFromDefFn, file_in_repo: str, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -298,7 +296,7 @@ def test_version_print_tag_prints_next_tag( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-tag", *force_args] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -326,7 +324,7 @@ def test_version_print_tag_prints_next_tag( def test_version_print_last_released_prints_version( repo_result: BuiltRepoResult, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -342,7 +340,7 @@ def test_version_print_last_released_prints_version( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -376,7 +374,7 @@ def test_version_print_last_released_prints_released_if_commits( repo_result: BuiltRepoResult, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, commits: list[str], - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, file_in_repo: str, @@ -397,7 +395,7 @@ def test_version_print_last_released_prints_released_if_commits( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -424,7 +422,7 @@ def test_version_print_last_released_prints_released_if_commits( ) def test_version_print_last_released_prints_nothing_if_no_tags( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, caplog: pytest.LogCaptureFixture, @@ -438,7 +436,7 @@ def test_version_print_last_released_prints_nothing_if_no_tags( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -469,7 +467,7 @@ def test_version_print_last_released_prints_nothing_if_no_tags( def test_version_print_last_released_on_detached_head( repo_result: BuiltRepoResult, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -488,7 +486,7 @@ def test_version_print_last_released_on_detached_head( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -516,7 +514,7 @@ def test_version_print_last_released_on_detached_head( def test_version_print_last_released_on_nonrelease_branch( repo_result: BuiltRepoResult, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -535,7 +533,7 @@ def test_version_print_last_released_on_nonrelease_branch( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -572,7 +570,7 @@ def test_version_print_last_released_tag_prints_correct_tag( repo_result: BuiltRepoResult, get_cfg_value_from_def: GetCfgValueFromDefFn, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -589,7 +587,7 @@ def test_version_print_last_released_tag_prints_correct_tag( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released-tag"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -631,7 +629,7 @@ def test_version_print_last_released_tag_prints_released_if_commits( get_cfg_value_from_def: GetCfgValueFromDefFn, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, commits: list[str], - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, file_in_repo: str, @@ -653,7 +651,7 @@ def test_version_print_last_released_tag_prints_released_if_commits( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released-tag"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -680,7 +678,7 @@ def test_version_print_last_released_tag_prints_released_if_commits( ) def test_version_print_last_released_tag_prints_nothing_if_no_tags( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, caplog: pytest.LogCaptureFixture, @@ -694,7 +692,7 @@ def test_version_print_last_released_tag_prints_nothing_if_no_tags( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released-tag"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -734,7 +732,7 @@ def test_version_print_last_released_tag_on_detached_head( repo_result: BuiltRepoResult, get_cfg_value_from_def: GetCfgValueFromDefFn, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -754,7 +752,7 @@ def test_version_print_last_released_tag_on_detached_head( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released-tag"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -791,7 +789,7 @@ def test_version_print_last_released_tag_on_nonrelease_branch( repo_result: BuiltRepoResult, get_cfg_value_from_def: GetCfgValueFromDefFn, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -811,7 +809,7 @@ def test_version_print_last_released_tag_on_nonrelease_branch( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released-tag"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -843,7 +841,7 @@ def test_version_print_last_released_tag_on_nonrelease_branch( ) def test_version_print_next_version_fails_on_detached_head( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, simulate_change_commits_n_rtn_changelog_entry: SimulateChangeCommitsNReturnChangelogEntryFn, get_commit_def_fn: GetCommitDefFn, mocked_git_push: MagicMock, @@ -870,7 +868,7 @@ def test_version_print_next_version_fails_on_detached_head( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -902,7 +900,7 @@ def test_version_print_next_version_fails_on_detached_head( ) def test_version_print_next_tag_fails_on_detached_head( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, simulate_change_commits_n_rtn_changelog_entry: SimulateChangeCommitsNReturnChangelogEntryFn, get_commit_def_fn: GetCommitDefFn, mocked_git_push: MagicMock, @@ -929,7 +927,7 @@ def test_version_print_next_tag_fails_on_detached_head( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-tag"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) diff --git a/tests/e2e/cmd_version/test_version_release_notes.py b/tests/e2e/cmd_version/test_version_release_notes.py index ccd82dc77..6786e1d3e 100644 --- a/tests/e2e/cmd_version/test_version_release_notes.py +++ b/tests/e2e/cmd_version/test_version_release_notes.py @@ -8,7 +8,6 @@ from freezegun import freeze_time from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture -from semantic_release.cli.commands.main import main from semantic_release.version.version import Version from tests.const import ( @@ -27,10 +26,9 @@ if TYPE_CHECKING: from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker - from tests.conftest import GetStableDateNowFn + from tests.conftest import GetStableDateNowFn, RunCliFn from tests.e2e.conftest import ( RetrieveRuntimeContextFn, ) @@ -54,7 +52,7 @@ def test_custom_release_notes_template( repo_result: BuiltRepoResult, next_release_version: str, - cli_runner: CliRunner, + run_cli: RunCliFn, use_release_notes_template: UseReleaseNotesTemplateFn, retrieve_runtime_context: RetrieveRuntimeContextFn, mocked_git_push: MagicMock, @@ -69,7 +67,7 @@ def test_custom_release_notes_template( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--vcs-release"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Must run this after the action because the release history object should be pulled from the # repository after a tag is created @@ -123,7 +121,7 @@ def test_custom_release_notes_template( ) def test_default_release_notes_license_statement( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, license_name: str, license_setting: str, update_pyproject_toml: UpdatePyprojectTomlFn, @@ -164,7 +162,7 @@ def test_default_release_notes_license_statement( # Act with freeze_time(now_datetime.astimezone(timezone.utc)): cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-changelog", "--vcs-release"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) diff --git a/tests/e2e/cmd_version/test_version_stamp.py b/tests/e2e/cmd_version/test_version_stamp.py index 9d45b6019..bad09d23b 100644 --- a/tests/e2e/cmd_version/test_version_stamp.py +++ b/tests/e2e/cmd_version/test_version_stamp.py @@ -11,7 +11,6 @@ from dotty_dict import Dotty from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture -from semantic_release.cli.commands.main import main from semantic_release.version.declarations.enum import VersionStampType from tests.const import EXAMPLE_PROJECT_NAME, MAIN_PROG_NAME, VERSION_SUBCMD @@ -29,8 +28,7 @@ if TYPE_CHECKING: from unittest.mock import MagicMock - from click.testing import CliRunner - + from tests.conftest import RunCliFn from tests.fixtures.example_project import ExProjectDir, UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuiltRepoResult @@ -58,7 +56,7 @@ def test_version_only_stamp_version( repo_result: BuiltRepoResult, expected_new_version: str, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: MagicMock, example_pyproject_toml: Path, @@ -93,7 +91,7 @@ def test_version_only_stamp_version( # Act (stamp the version but also create the changelog) cli_cmd = [*VERSION_STAMP_CMD, "--minor"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command head_after = repo.head.commit @@ -145,7 +143,7 @@ def test_version_only_stamp_version( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_stamp_version_variables_python( - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, example_project_dir: ExProjectDir, ) -> None: @@ -162,7 +160,7 @@ def test_stamp_version_variables_python( # Act cli_cmd = VERSION_STAMP_CMD - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Check the result assert_successful_exit_code(result, cli_cmd) @@ -178,7 +176,7 @@ def test_stamp_version_variables_python( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_stamp_version_toml( - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, default_tag_format_str: str, ) -> None: @@ -213,7 +211,7 @@ def test_stamp_version_toml( # Act cli_cmd = VERSION_STAMP_CMD - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Check the result assert_successful_exit_code(result, cli_cmd) @@ -234,7 +232,7 @@ def test_stamp_version_toml( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_stamp_version_variables_yaml( - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, ) -> None: orig_version = "0.0.0" @@ -258,7 +256,7 @@ def test_stamp_version_variables_yaml( # Act cli_cmd = VERSION_STAMP_CMD - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Check the result assert_successful_exit_code(result, cli_cmd) @@ -277,7 +275,7 @@ def test_stamp_version_variables_yaml( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_stamp_version_variables_yaml_cff( - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, ) -> None: """ @@ -314,7 +312,7 @@ def test_stamp_version_variables_yaml_cff( # Act cli_cmd = VERSION_STAMP_CMD - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Check the result assert_successful_exit_code(result, cli_cmd) @@ -333,7 +331,7 @@ def test_stamp_version_variables_yaml_cff( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_stamp_version_variables_json( - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, ) -> None: orig_version = "0.0.0" @@ -356,7 +354,7 @@ def test_stamp_version_variables_json( # Act cli_cmd = VERSION_STAMP_CMD - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Check the result assert_successful_exit_code(result, cli_cmd) @@ -375,7 +373,7 @@ def test_stamp_version_variables_json( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_stamp_version_variables_yaml_github_actions( - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, default_tag_format_str: str, ) -> None: @@ -425,7 +423,7 @@ def test_stamp_version_variables_yaml_github_actions( # Act cli_cmd = VERSION_STAMP_CMD - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Check the result assert_successful_exit_code(result, cli_cmd) @@ -447,7 +445,7 @@ def test_stamp_version_variables_yaml_github_actions( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_stamp_version_variables_yaml_kustomization_container_spec( - cli_runner: CliRunner, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, default_tag_format_str: str, ) -> None: @@ -483,7 +481,7 @@ def test_stamp_version_variables_yaml_kustomization_container_spec( # Act cli_cmd = VERSION_STAMP_CMD - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Check the result assert_successful_exit_code(result, cli_cmd) diff --git a/tests/e2e/cmd_version/test_version_strict.py b/tests/e2e/cmd_version/test_version_strict.py index c8dcb56a5..a0ff9bb8d 100644 --- a/tests/e2e/cmd_version/test_version_strict.py +++ b/tests/e2e/cmd_version/test_version_strict.py @@ -5,8 +5,6 @@ import pytest from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture -from semantic_release.cli.commands.main import main - from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD from tests.fixtures.repos import repo_w_trunk_only_conventional_commits from tests.util import assert_exit_code @@ -14,9 +12,9 @@ if TYPE_CHECKING: from unittest.mock import MagicMock - from click.testing import CliRunner from requests_mock import Mocker + from tests.conftest import RunCliFn from tests.fixtures.git_repo import BuiltRepoResult, GetVersionsFromRepoBuildDefFn @@ -27,7 +25,7 @@ def test_version_already_released_when_strict( repo_result: BuiltRepoResult, get_versions_from_repo_build_def: GetVersionsFromRepoBuildDefFn, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -50,7 +48,7 @@ def test_version_already_released_when_strict( # Act cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -75,7 +73,7 @@ def test_version_already_released_when_strict( ) def test_version_on_nonrelease_branch_when_strict( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, ): @@ -98,7 +96,7 @@ def test_version_on_nonrelease_branch_when_strict( # Act cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_exit_code(2, result, cli_cmd) diff --git a/tests/e2e/test_help.py b/tests/e2e/test_help.py index a31454efd..0119586d0 100644 --- a/tests/e2e/test_help.py +++ b/tests/e2e/test_help.py @@ -17,9 +17,8 @@ if TYPE_CHECKING: from click import Command - from click.testing import CliRunner - from git import Repo + from tests.conftest import RunCliFn from tests.fixtures import UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuiltRepoResult @@ -39,7 +38,7 @@ def test_help_no_repo( help_option: str, command: Command, - cli_runner: CliRunner, + run_cli: RunCliFn, change_to_ex_proj_dir: None, ): """ @@ -70,7 +69,7 @@ def test_help_no_repo( ) # Run the command with the help option - result = cli_runner.invoke(main, args, prog_name=MAIN_PROG_NAME) + result = run_cli(args, invoke_kwargs={"prog_name": MAIN_PROG_NAME}) # Evaluate result assert_exit_code(HELP_EXIT_CODE, result, [MAIN_PROG_NAME, *args]) @@ -89,7 +88,7 @@ def test_help_no_repo( def test_help_valid_config( help_option: str, command: Command, - cli_runner: CliRunner, + run_cli: RunCliFn, ): """ Test that the help message is displayed when the current directory is a git repository @@ -118,7 +117,7 @@ def test_help_valid_config( ) # Run the command with the help option - result = cli_runner.invoke(main, args, prog_name=MAIN_PROG_NAME) + result = run_cli(args, invoke_kwargs={"prog_name": MAIN_PROG_NAME}) # Evaluate result assert_exit_code(HELP_EXIT_CODE, result, [MAIN_PROG_NAME, *args]) @@ -133,11 +132,11 @@ def test_help_valid_config( (main, changelog, generate_config, publish, version), ids=lambda cmd: cmd.name, ) +@pytest.mark.usefixtures(repo_w_trunk_only_conventional_commits.__name__) def test_help_invalid_config( help_option: str, command: Command, - cli_runner: CliRunner, - repo_w_trunk_only_conventional_commits: Repo, + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn, ): """ @@ -171,7 +170,7 @@ def test_help_invalid_config( ) # Run the command with the help option - result = cli_runner.invoke(main, args, prog_name=MAIN_PROG_NAME) + result = run_cli(args, invoke_kwargs={"prog_name": MAIN_PROG_NAME}) # Evaluate result assert_exit_code(HELP_EXIT_CODE, result, [MAIN_PROG_NAME, *args]) @@ -192,7 +191,7 @@ def test_help_invalid_config( def test_help_non_release_branch( help_option: str, command: Command, - cli_runner: CliRunner, + run_cli: RunCliFn, repo_result: BuiltRepoResult, ): """ @@ -226,7 +225,7 @@ def test_help_non_release_branch( ) # Run the command with the help option - result = cli_runner.invoke(main, args, prog_name=MAIN_PROG_NAME) + result = run_cli(args, invoke_kwargs={"prog_name": MAIN_PROG_NAME}) # Evaluate result assert_exit_code(HELP_EXIT_CODE, result, [MAIN_PROG_NAME, *args]) diff --git a/tests/e2e/test_main.py b/tests/e2e/test_main.py index fc65c7f21..ff290146e 100644 --- a/tests/e2e/test_main.py +++ b/tests/e2e/test_main.py @@ -11,7 +11,6 @@ from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture from semantic_release import __version__ -from semantic_release.cli.commands.main import main from tests.const import MAIN_PROG_NAME, SUCCESS_EXIT_CODE, VERSION_SUBCMD from tests.fixtures.repos import repo_w_no_tags_conventional_commits @@ -20,8 +19,7 @@ if TYPE_CHECKING: from pathlib import Path - from click.testing import CliRunner - + from tests.conftest import RunCliFn from tests.fixtures.example_project import ExProjectDir, UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuiltRepoResult @@ -50,20 +48,19 @@ def test_entrypoint_scripts(project_script_name: str): assert not proc.stderr -def test_main_prints_version_and_exits(cli_runner: CliRunner): +def test_main_prints_version_and_exits(run_cli: RunCliFn): cli_cmd = [MAIN_PROG_NAME, "--version"] # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) assert result.output == f"semantic-release, version {__version__}\n" -def test_main_no_args_prints_help_text(cli_runner: CliRunner): - result = cli_runner.invoke(main, []) - assert_successful_exit_code(result, [MAIN_PROG_NAME]) +def test_main_no_args_prints_help_text(run_cli: RunCliFn): + assert_successful_exit_code(run_cli(), [MAIN_PROG_NAME]) @pytest.mark.parametrize( @@ -71,14 +68,14 @@ def test_main_no_args_prints_help_text(cli_runner: CliRunner): [lazy_fixture(repo_w_no_tags_conventional_commits.__name__)], ) def test_not_a_release_branch_exit_code( - repo_result: BuiltRepoResult, cli_runner: CliRunner + repo_result: BuiltRepoResult, run_cli: RunCliFn ): # Run anything that doesn't trigger the help text repo_result["repo"].git.checkout("-b", "branch-does-not-exist") # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-commit"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -90,14 +87,14 @@ def test_not_a_release_branch_exit_code( ) def test_not_a_release_branch_exit_code_with_strict( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, ): # Run anything that doesn't trigger the help text repo_result["repo"].git.checkout("-b", "branch-does-not-exist") # Act cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, "--no-commit"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_exit_code(2, result, cli_cmd) @@ -109,7 +106,7 @@ def test_not_a_release_branch_exit_code_with_strict( ) def test_not_a_release_branch_detached_head_exit_code( repo_result: BuiltRepoResult, - cli_runner: CliRunner, + run_cli: RunCliFn, ): expected_err_msg = ( "Detached HEAD state cannot match any release groups; no release will be made" @@ -120,7 +117,7 @@ def test_not_a_release_branch_detached_head_exit_code( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-commit"] - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # detached head states should throw an error as release branches cannot be determined assert_exit_code(1, result, cli_cmd) @@ -153,7 +150,7 @@ def json_file_with_no_configuration_for_psr(tmp_path: Path) -> Path: @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_default_config_is_used_when_none_in_toml_config_file( - cli_runner: CliRunner, + run_cli: RunCliFn, toml_file_with_no_configuration_for_psr: Path, ): cli_cmd = [ @@ -165,7 +162,7 @@ def test_default_config_is_used_when_none_in_toml_config_file( ] # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -173,7 +170,7 @@ def test_default_config_is_used_when_none_in_toml_config_file( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_default_config_is_used_when_none_in_json_config_file( - cli_runner: CliRunner, + run_cli: RunCliFn, json_file_with_no_configuration_for_psr: Path, ): cli_cmd = [ @@ -185,7 +182,7 @@ def test_default_config_is_used_when_none_in_json_config_file( ] # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) @@ -193,7 +190,7 @@ def test_default_config_is_used_when_none_in_json_config_file( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_errors_when_config_file_does_not_exist_and_passed_explicitly( - cli_runner: CliRunner, + run_cli: RunCliFn, ): cli_cmd = [ MAIN_PROG_NAME, @@ -204,7 +201,7 @@ def test_errors_when_config_file_does_not_exist_and_passed_explicitly( ] # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_exit_code(2, result, cli_cmd) @@ -213,14 +210,14 @@ def test_errors_when_config_file_does_not_exist_and_passed_explicitly( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_errors_when_config_file_invalid_configuration( - cli_runner: CliRunner, update_pyproject_toml: UpdatePyprojectTomlFn + run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn ): # Setup update_pyproject_toml("tool.semantic_release.remote.type", "invalidType") cli_cmd = [MAIN_PROG_NAME, "--config", "pyproject.toml", VERSION_SUBCMD] # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # preprocess results stderr_lines = result.stderr.splitlines() @@ -232,7 +229,7 @@ def test_errors_when_config_file_invalid_configuration( def test_uses_default_config_when_no_config_file_found( - cli_runner: CliRunner, + run_cli: RunCliFn, example_project_dir: ExProjectDir, change_to_ex_proj_dir: None, ): @@ -252,7 +249,7 @@ def test_uses_default_config_when_no_config_file_found( cli_cmd = [MAIN_PROG_NAME, "--noop", VERSION_SUBCMD] # Act - result = cli_runner.invoke(main, cli_cmd[1:]) + result = run_cli(cli_cmd[1:]) # Evaluate assert_successful_exit_code(result, cli_cmd) diff --git a/tests/fixtures/git_repo.py b/tests/fixtures/git_repo.py index be1ee0fa0..391799e59 100644 --- a/tests/fixtures/git_repo.py +++ b/tests/fixtures/git_repo.py @@ -1032,7 +1032,9 @@ def _build_configured_base_repo( # noqa: C901 raise ValueError(f"Unknown HVCS client name: {hvcs_client_name}") # Create HVCS Client instance - hvcs = hvcs_class(example_git_https_url, hvcs_domain=hvcs_domain) + with mock.patch.dict(os.environ, {}, clear=True): + hvcs = hvcs_class(example_git_https_url, hvcs_domain=hvcs_domain) + assert hvcs.repo_name # Force the HVCS client to cache the repo name # Set tag format in configuration if tag_format_str is not None: diff --git a/tests/util.py b/tests/util.py index 63d7679ac..3d4815064 100644 --- a/tests/util.py +++ b/tests/util.py @@ -66,14 +66,13 @@ def assert_exit_code( "", # Explain what command failed "Unexpected exit code from command:", - # f" '{str.join(' ', cli_cmd)}'", indent(f"'{str.join(' ', cli_cmd)}'", " " * 2), "", # Add indentation to each line for stdout & stderr "stdout:", - indent(result.stdout, " " * 2), + indent(result.stdout, " " * 2) if result.stdout.strip() else "", "stderr:", - indent(result.stderr, " " * 2), + indent(result.stderr, " " * 2) if result.stderr.strip() else "", ], ) ) From b03daec33082d1ea6db0ba5606014b5673ec0e6f Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 23:31:10 -0600 Subject: [PATCH 068/120] test(fixtures): update repos with merges to ignore merge commits in changelog --- tests/fixtures/git_repo.py | 1 + .../repos/git_flow/repo_w_1_release_channel.py | 15 ++++++++------- .../repos/git_flow/repo_w_2_release_channels.py | 11 ++++++----- .../repos/git_flow/repo_w_3_release_channels.py | 13 +++++++------ .../repos/git_flow/repo_w_4_release_channels.py | 13 +++++++------ .../repos/github_flow/repo_w_default_release.py | 5 +++-- .../repos/github_flow/repo_w_release_channels.py | 5 +++-- tests/fixtures/repos/repo_initial_commit.py | 5 +++-- .../repo_w_dual_version_support.py | 5 +++-- .../repo_w_dual_version_support_w_prereleases.py | 5 +++-- .../repos/trunk_based_dev/repo_w_no_tags.py | 5 +++-- .../repos/trunk_based_dev/repo_w_prereleases.py | 5 +++-- .../fixtures/repos/trunk_based_dev/repo_w_tags.py | 5 +++-- 13 files changed, 53 insertions(+), 40 deletions(-) diff --git a/tests/fixtures/git_repo.py b/tests/fixtures/git_repo.py index 391799e59..60d0b4bad 100644 --- a/tests/fixtures/git_repo.py +++ b/tests/fixtures/git_repo.py @@ -344,6 +344,7 @@ def __call__( tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, # Default as of v10 ) -> Sequence[RepoActions]: ... class BuildRepoFromDefinitionFn(Protocol): diff --git a/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py b/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py index 36a2eb3d1..9a1669888 100644 --- a/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py +++ b/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py @@ -103,6 +103,7 @@ def _get_repo_from_defintion( tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -156,7 +157,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEFAULT_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -272,7 +273,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -359,7 +360,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -464,7 +465,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -551,7 +552,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -638,7 +639,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -694,7 +695,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), diff --git a/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py index 22ccd7083..9d5863a25 100644 --- a/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py @@ -103,6 +103,7 @@ def _get_repo_from_defintion( tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -156,7 +157,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEFAULT_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -278,7 +279,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -463,7 +464,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -550,7 +551,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -637,7 +638,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), diff --git a/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py index ba54e64e8..1e7f2e04d 100644 --- a/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py @@ -105,6 +105,7 @@ def _get_repo_from_defintion( tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -158,7 +159,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEFAULT_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -286,7 +287,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -454,7 +455,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -627,7 +628,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -683,7 +684,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -762,7 +763,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), diff --git a/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py index f8128015a..2c8d24e5f 100644 --- a/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py @@ -115,6 +115,7 @@ def _get_repo_from_defintion( tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -182,7 +183,7 @@ def _get_repo_from_defintion( tgt_branch_name=BETA_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -209,7 +210,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEFAULT_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -351,7 +352,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -446,7 +447,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -556,7 +557,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -690,7 +691,7 @@ def _get_repo_from_defintion( tgt_branch_name=DEV_BRANCH_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), diff --git a/tests/fixtures/repos/github_flow/repo_w_default_release.py b/tests/fixtures/repos/github_flow/repo_w_default_release.py index 7eddbd852..b0c2da302 100644 --- a/tests/fixtures/repos/github_flow/repo_w_default_release.py +++ b/tests/fixtures/repos/github_flow/repo_w_default_release.py @@ -91,13 +91,14 @@ def get_repo_definition_4_github_flow_repo_w_default_release_channel( for a single release channel on the default branch. """ - def _get_repo_from_defintion( + def _get_repo_from_definition( commit_type: CommitConvention, hvcs_client_name: str = "github", hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -388,7 +389,7 @@ def _get_repo_from_defintion( return repo_construction_steps - return _get_repo_from_defintion + return _get_repo_from_definition @pytest.fixture(scope="session") diff --git a/tests/fixtures/repos/github_flow/repo_w_release_channels.py b/tests/fixtures/repos/github_flow/repo_w_release_channels.py index c0670a968..4549ffbf6 100644 --- a/tests/fixtures/repos/github_flow/repo_w_release_channels.py +++ b/tests/fixtures/repos/github_flow/repo_w_release_channels.py @@ -98,6 +98,7 @@ def _get_repo_from_defintion( tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -315,7 +316,7 @@ def _get_repo_from_defintion( branch_name=FIX_BRANCH_1_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), @@ -419,7 +420,7 @@ def _get_repo_from_defintion( branch_name=FEAT_BRANCH_1_NAME, ), "datetime": next(commit_timestamp_gen), - "include_in_changelog": bool(commit_type == "emoji"), + "include_in_changelog": not ignore_merge_commits, }, commit_type, ), diff --git a/tests/fixtures/repos/repo_initial_commit.py b/tests/fixtures/repos/repo_initial_commit.py index c6ffd952b..817386ba7 100644 --- a/tests/fixtures/repos/repo_initial_commit.py +++ b/tests/fixtures/repos/repo_initial_commit.py @@ -71,13 +71,14 @@ def get_repo_definition_4_repo_w_initial_commit( changelog_rst_file: Path, stable_now_date: GetStableDateNowFn, ) -> GetRepoDefinitionFn: - def _get_repo_from_defintion( + def _get_repo_from_definition( commit_type: CommitConvention, hvcs_client_name: str = "github", hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: repo_construction_steps: list[RepoActions] = [] repo_construction_steps.extend( @@ -142,7 +143,7 @@ def _get_repo_from_defintion( return repo_construction_steps - return _get_repo_from_defintion + return _get_repo_from_definition @pytest.fixture(scope="session") diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py index 04f0bc27c..83665399c 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py @@ -85,13 +85,14 @@ def get_repo_definition_4_trunk_only_repo_w_dual_version_support( only official releases with latest and previous version support. """ - def _get_repo_from_defintion( + def _get_repo_from_definition( commit_type: CommitConvention, hvcs_client_name: str = "github", hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -358,7 +359,7 @@ def _get_repo_from_defintion( return repo_construction_steps - return _get_repo_from_defintion + return _get_repo_from_definition @pytest.fixture(scope="session") diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py index d0458cb90..7f57d6e01 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py @@ -85,13 +85,14 @@ def get_repo_definition_4_trunk_only_repo_w_dual_version_spt_w_prereleases( only official releases with latest and previous version support. """ - def _get_repo_from_defintion( + def _get_repo_from_definition( commit_type: CommitConvention, hvcs_client_name: str = "github", hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -441,7 +442,7 @@ def _get_repo_from_defintion( return repo_construction_steps - return _get_repo_from_defintion + return _get_repo_from_definition @pytest.fixture(scope="session") diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py index 65acc4d50..cdb5c2afc 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py @@ -78,13 +78,14 @@ def get_repo_definition_4_trunk_only_repo_w_no_tags( any releases. """ - def _get_repo_from_defintion( + def _get_repo_from_definition( commit_type: CommitConvention, hvcs_client_name: str = "github", hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -174,7 +175,7 @@ def _get_repo_from_defintion( return repo_construction_steps - return _get_repo_from_defintion + return _get_repo_from_definition @pytest.fixture(scope="session") diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py b/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py index 8ac0b0674..ad4515268 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py @@ -79,13 +79,14 @@ def get_repo_definition_4_trunk_only_repo_w_prerelease_tags( official and prereleases releases. """ - def _get_repo_from_defintion( + def _get_repo_from_definition( commit_type: CommitConvention, hvcs_client_name: str = "github", hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -300,7 +301,7 @@ def _get_repo_from_defintion( return repo_construction_steps - return _get_repo_from_defintion + return _get_repo_from_definition @pytest.fixture(scope="session") diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py b/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py index c8bfd35d1..08ed83be3 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py @@ -81,13 +81,14 @@ def get_repo_definition_4_trunk_only_repo_w_tags( only official releases. """ - def _get_repo_from_defintion( + def _get_repo_from_definition( commit_type: CommitConvention, hvcs_client_name: str = "github", hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, mask_initial_release: bool = False, + ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() commit_timestamp_gen = ( @@ -222,7 +223,7 @@ def _get_repo_from_defintion( return repo_construction_steps - return _get_repo_from_defintion + return _get_repo_from_definition @pytest.fixture(scope="session") From 5acac1b503164ad7f491904406b6b9a7297c2789 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 18 May 2025 23:33:18 -0600 Subject: [PATCH 069/120] refactor(cli): configure only PSR for logging & not root logger --- src/semantic_release/cli/commands/main.py | 26 +++++++++++------------ 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/semantic_release/cli/commands/main.py b/src/semantic_release/cli/commands/main.py index 7f0d170e2..3286b119f 100644 --- a/src/semantic_release/cli/commands/main.py +++ b/src/semantic_release/cli/commands/main.py @@ -21,7 +21,7 @@ # pass -FORMAT = "[%(module)s.%(funcName)s] %(message)s" +FORMAT = "%(message)s" class Cli(click.MultiCommand): @@ -107,8 +107,6 @@ def main( For more information, visit https://python-semantic-release.readthedocs.io/ """ - console = Console(stderr=True) - log_levels = [ SemanticReleaseLogLevels.WARNING, SemanticReleaseLogLevels.INFO, @@ -118,18 +116,18 @@ def main( globals.log_level = log_levels[verbosity] - logging.basicConfig( - level=globals.log_level, - format=FORMAT, - datefmt="[%X]", - handlers=[ - RichHandler( - console=console, rich_tracebacks=True, tracebacks_suppress=[click] - ), - ], + # Set up our pretty console formatter + rich_handler = RichHandler( + console=Console(stderr=True), rich_tracebacks=True, tracebacks_suppress=[click] ) - - logger = logging.getLogger(__name__) + rich_handler.setFormatter(logging.Formatter(FORMAT, datefmt="[%X]")) + + # Set up logging with our pretty console formatter + logger = logging.getLogger(semantic_release.__package__) + logger.handlers.clear() + logger.filters.clear() + logger.addHandler(rich_handler) + logger.setLevel(globals.log_level) logger.debug("logging level set to: %s", logging.getLevelName(globals.log_level)) if noop: From 5ba71f5534888c9548f15d2b134e5223d335b96e Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 00:12:43 -0600 Subject: [PATCH 070/120] refactor: centralize logging object across package --- .../changelog/release_history.py | 24 +++++----- src/semantic_release/changelog/template.py | 13 +++-- src/semantic_release/cli/changelog_writer.py | 9 ++-- .../cli/commands/changelog.py | 9 ++-- src/semantic_release/cli/commands/main.py | 2 +- src/semantic_release/cli/commands/publish.py | 7 +-- src/semantic_release/cli/commands/version.py | 39 ++++++++------- src/semantic_release/cli/config.py | 18 +++---- .../cli/github_actions_output.py | 6 +-- src/semantic_release/cli/masking_filter.py | 18 ++++--- src/semantic_release/cli/util.py | 14 +++--- src/semantic_release/commit_parser/angular.py | 5 +- .../commit_parser/conventional.py | 4 +- src/semantic_release/commit_parser/emoji.py | 4 +- src/semantic_release/commit_parser/scipy.py | 4 +- src/semantic_release/commit_parser/tag.py | 4 +- src/semantic_release/gitproject.py | 4 +- src/semantic_release/globals.py | 10 ++++ src/semantic_release/helpers.py | 18 +++---- src/semantic_release/hvcs/_base.py | 5 -- src/semantic_release/hvcs/bitbucket.py | 8 +--- src/semantic_release/hvcs/gitea.py | 44 ++++++++--------- src/semantic_release/hvcs/github.py | 48 +++++++++---------- src/semantic_release/hvcs/gitlab.py | 33 +++++-------- src/semantic_release/hvcs/remote_hvcs_base.py | 5 -- src/semantic_release/hvcs/util.py | 5 +- src/semantic_release/version/algorithm.py | 4 +- src/semantic_release/version/declaration.py | 11 ++--- .../version/declarations/pattern.py | 11 ++--- .../version/declarations/toml.py | 11 ++--- src/semantic_release/version/translator.py | 6 +-- src/semantic_release/version/version.py | 15 +++--- 32 files changed, 183 insertions(+), 235 deletions(-) diff --git a/src/semantic_release/changelog/release_history.py b/src/semantic_release/changelog/release_history.py index 887fa5492..b947a66ad 100644 --- a/src/semantic_release/changelog/release_history.py +++ b/src/semantic_release/changelog/release_history.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging from collections import defaultdict from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING, TypedDict @@ -11,6 +10,7 @@ from semantic_release.commit_parser.token import ParsedCommit from semantic_release.commit_parser.util import force_str from semantic_release.enums import LevelBump +from semantic_release.globals import logger from semantic_release.helpers import validate_types_in_sequence from semantic_release.version.algorithm import tags_and_versions @@ -29,8 +29,6 @@ from semantic_release.version.translator import VersionTranslator from semantic_release.version.version import Version -log = logging.getLogger(__name__) - class ReleaseHistory: @classmethod @@ -72,17 +70,17 @@ def from_git_history( for commit in repo.iter_commits("HEAD", topo_order=True): # Determine if we have found another release - log.debug("checking if commit %s matches any tags", commit.hexsha[:7]) + logger.debug("checking if commit %s matches any tags", commit.hexsha[:7]) t_v = tag_sha_2_version_lookup.get(commit.hexsha, None) if t_v is None: - log.debug("no tags correspond to commit %s", commit.hexsha) + logger.debug("no tags correspond to commit %s", commit.hexsha) else: # Unpack the tuple (overriding the current version) tag, the_version = t_v # we have found the latest commit introduced by this tag # so we create a new Release entry - log.debug("found commit %s for tag %s", commit.hexsha, tag.name) + logger.debug("found commit %s for tag %s", commit.hexsha, tag.name) # tag.object is a Commit if the tag is lightweight, otherwise # it is a TagObject with additional metadata about the tag @@ -110,7 +108,7 @@ def from_git_history( released.setdefault(the_version, release) - log.info( + logger.info( "parsing commit [%s] %s", commit.hexsha[:8], str(commit.message).replace("\n", " ")[:54], @@ -153,7 +151,7 @@ def from_git_history( if isinstance(parsed_result, ParseError) else parsed_result.type ) - log.debug("commit has type '%s'", commit_type) + logger.debug("commit has type '%s'", commit_type) has_exclusion_match = any( pattern.match(commit_message) for pattern in exclude_commit_patterns @@ -166,7 +164,7 @@ def from_git_history( ) if ignore_merge_commits and parsed_result.is_merge_commit(): - log.info("Excluding merge commit[%s]", parsed_result.short_hash) + logger.info("Excluding merge commit[%s]", parsed_result.short_hash) continue # Skip excluded commits except for any commit causing a version bump @@ -174,7 +172,7 @@ def from_git_history( # are included, then the changelog will be empty. Even if ther was other # commits included, the true reason for a version bump would be missing. if has_exclusion_match and commit_level_bump == LevelBump.NO_RELEASE: - log.info( + logger.info( "Excluding %s commit[%s] %s", "piece of squashed" if is_squash_commit else "", parsed_result.short_hash, @@ -186,7 +184,7 @@ def from_git_history( isinstance(parsed_result, ParsedCommit) and not parsed_result.include_in_changelog ): - log.info( + logger.info( str.join( " ", [ @@ -199,7 +197,7 @@ def from_git_history( continue if the_version is None: - log.info( + logger.info( "[Unreleased] adding commit[%s] to unreleased '%s'", parsed_result.short_hash, commit_type, @@ -207,7 +205,7 @@ def from_git_history( unreleased[commit_type].append(parsed_result) continue - log.info( + logger.info( "[%s] adding commit[%s] to release '%s'", the_version, parsed_result.short_hash, diff --git a/src/semantic_release/changelog/template.py b/src/semantic_release/changelog/template.py index 2b80d8f65..d74295404 100644 --- a/src/semantic_release/changelog/template.py +++ b/src/semantic_release/changelog/template.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging import os import shutil from pathlib import Path, PurePosixPath @@ -9,6 +8,7 @@ from jinja2 import FileSystemLoader from jinja2.sandbox import SandboxedEnvironment +from semantic_release.globals import logger from semantic_release.helpers import dynamic_import if TYPE_CHECKING: # pragma: no cover @@ -17,9 +17,6 @@ from jinja2 import Environment -log = logging.getLogger(__name__) - - # pylint: disable=too-many-arguments,too-many-locals def environment( template_dir: Path | str = ".", @@ -107,7 +104,7 @@ def recursive_render( and not file.startswith(".") ): output_path = (_root_dir / root.relative_to(template_dir)).resolve() - log.info("Rendering templates from %s to %s", root, output_path) + logger.info("Rendering templates from %s to %s", root, output_path) output_path.mkdir(parents=True, exist_ok=True) if file.endswith(".j2"): # We know the file ends with .j2 by the filter in the for-loop @@ -122,18 +119,20 @@ def recursive_render( # contents of a file during the rendering of the template. This mechanism # is used for inserting into a current changelog. When using stream rendering # of the same file, it always came back empty - log.debug("rendering %s to %s", src_file_path, output_file_path) + logger.debug("rendering %s to %s", src_file_path, output_file_path) rendered_file = environment.get_template(src_file_path).render().rstrip() with open(output_file_path, "w", encoding="utf-8") as output_file: output_file.write(f"{rendered_file}\n") rendered_paths.append(output_file_path) + else: src_file = str((root / file).resolve()) target_file = str((output_path / file).resolve()) - log.debug( + logger.debug( "source file %s is not a template, copying to %s", src_file, target_file ) shutil.copyfile(src_file, target_file) rendered_paths.append(target_file) + return rendered_paths diff --git a/src/semantic_release/cli/changelog_writer.py b/src/semantic_release/cli/changelog_writer.py index 5ee86457e..96020b73a 100644 --- a/src/semantic_release/cli/changelog_writer.py +++ b/src/semantic_release/cli/changelog_writer.py @@ -2,7 +2,6 @@ import os from contextlib import suppress -from logging import getLogger from pathlib import Path from typing import TYPE_CHECKING @@ -25,6 +24,7 @@ ) from semantic_release.cli.util import noop_report from semantic_release.errors import InternalError +from semantic_release.globals import logger from semantic_release.helpers import sort_numerically if TYPE_CHECKING: # pragma: no cover @@ -36,9 +36,6 @@ from semantic_release.hvcs._base import HvcsBase -log = getLogger(__name__) - - def get_default_tpl_dir(style: str, sub_dir: str | None = None) -> Path: module_base_path = Path(str(files(semantic_release.__name__))) default_templates_path = module_base_path.joinpath( @@ -210,7 +207,9 @@ def write_changelog_files( noop=noop, ) - log.info("No contents found in %r, using default changelog template", template_dir) + logger.info( + "No contents found in %r, using default changelog template", template_dir + ) return [ write_default_changelog( changelog_file=runtime_ctx.changelog_file, diff --git a/src/semantic_release/cli/commands/changelog.py b/src/semantic_release/cli/commands/changelog.py index 316b44450..523c1d2a5 100644 --- a/src/semantic_release/cli/commands/changelog.py +++ b/src/semantic_release/cli/commands/changelog.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging from contextlib import suppress from pathlib import Path from typing import TYPE_CHECKING @@ -15,15 +14,13 @@ write_changelog_files, ) from semantic_release.cli.util import noop_report +from semantic_release.globals import logger from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase if TYPE_CHECKING: # pragma: no cover from semantic_release.cli.cli_context import CliContextObj -log = logging.getLogger(__name__) - - def get_license_name_for_release(tag_name: str, project_root: Path) -> str: # Retrieve the license name at the time of the specific release tag project_metadata: dict[str, str] = {} @@ -174,7 +171,7 @@ def changelog(cli_ctx: CliContextObj, release_tag: str | None) -> None: hvcs_client=hvcs_client, noop=runtime.global_cli_options.noop, ) - except Exception as e: - log.exception(e) + except Exception as e: # noqa: BLE001 # TODO: catch specific exceptions + logger.exception(e) click.echo("Failed to post release notes to remote", err=True) ctx.exit(1) diff --git a/src/semantic_release/cli/commands/main.py b/src/semantic_release/cli/commands/main.py index 3286b119f..08b893d70 100644 --- a/src/semantic_release/cli/commands/main.py +++ b/src/semantic_release/cli/commands/main.py @@ -123,7 +123,7 @@ def main( rich_handler.setFormatter(logging.Formatter(FORMAT, datefmt="[%X]")) # Set up logging with our pretty console formatter - logger = logging.getLogger(semantic_release.__package__) + logger = globals.logger logger.handlers.clear() logger.filters.clear() logger.addHandler(rich_handler) diff --git a/src/semantic_release/cli/commands/publish.py b/src/semantic_release/cli/commands/publish.py index 0d354c387..4efab72de 100644 --- a/src/semantic_release/cli/commands/publish.py +++ b/src/semantic_release/cli/commands/publish.py @@ -1,12 +1,12 @@ from __future__ import annotations -import logging from typing import TYPE_CHECKING import click from git import Repo from semantic_release.cli.util import noop_report +from semantic_release.globals import logger from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase from semantic_release.version.algorithm import tags_and_versions @@ -14,9 +14,6 @@ from semantic_release.cli.cli_context import CliContextObj -log = logging.getLogger(__name__) - - def publish_distributions( tag: str, hvcs_client: RemoteHvcsBase, @@ -36,7 +33,7 @@ def publish_distributions( ) return - log.info("Uploading distributions to release") + logger.info("Uploading distributions to release") for pattern in dist_glob_patterns: hvcs_client.upload_dists(tag=tag, dist_glob=pattern) # type: ignore[attr-defined] diff --git a/src/semantic_release/cli/commands/version.py b/src/semantic_release/cli/commands/version.py index 86d209937..66b42c4cb 100644 --- a/src/semantic_release/cli/commands/version.py +++ b/src/semantic_release/cli/commands/version.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging import os import subprocess import sys @@ -30,6 +29,7 @@ UnexpectedResponse, ) from semantic_release.gitproject import GitProject +from semantic_release.globals import logger from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase from semantic_release.version.algorithm import ( next_version, @@ -48,9 +48,6 @@ from semantic_release.version.version import Version -log = logging.getLogger(__name__) - - def is_forced_prerelease( as_prerelease: bool, forced_level_bump: LevelBump | None, prerelease: bool ) -> bool: @@ -62,7 +59,7 @@ def is_forced_prerelease( Otherwise (``force_level is None``) use the value of ``prerelease`` """ local_vars = list(locals().items()) - log.debug( + logger.debug( "%s: %s", is_forced_prerelease.__name__, str.join(", ", iter(f"{k} = {v}" for k, v in local_vars)), @@ -143,7 +140,7 @@ def apply_version_to_source_files( return [] if not noop: - log.debug("Updating version %s in repository files...", version) + logger.debug("Updating version %s in repository files...", version) paths = list( map( @@ -181,8 +178,8 @@ def shell( try: shell, _ = shellingham.detect_shell() except shellingham.ShellDetectionFailure: - log.warning("failed to detect shell, using default shell: %s", DEFAULT_SHELL) - log.debug("stack trace", exc_info=True) + logger.warning("failed to detect shell, using default shell: %s", DEFAULT_SHELL) + logger.debug("stack trace", exc_info=True) shell = DEFAULT_SHELL if not shell: @@ -261,7 +258,7 @@ def build_distributions( noop_report(f"would have run the build_command {build_command}") return - log.info("Running build command %s", build_command) + logger.info("Running build command %s", build_command) rprint(f"[bold green]:hammer_and_wrench: Running build command: {build_command}") build_env_vars: dict[str, str] = dict( @@ -295,8 +292,8 @@ def build_distributions( shell(build_command, env=build_env_vars, check=True) rprint("[bold green]Build completed successfully!") except subprocess.CalledProcessError as exc: - log.exception(exc) - log.error("Build command failed with exit code %s", exc.returncode) # noqa: TRY400 + logger.exception(exc) + logger.error("Build command failed with exit code %s", exc.returncode) # noqa: TRY400 raise BuildDistributionsError from exc @@ -453,7 +450,7 @@ def version( # noqa: C901 if not ( last_release := last_released(config.repo_dir, tag_format=config.tag_format) ): - log.warning("No release tags found.") + logger.warning("No release tags found.") return click.echo(last_release[0] if print_last_released_tag else last_release[1]) @@ -482,22 +479,24 @@ def version( # noqa: C901 ) if prerelease_token: - log.info("Forcing use of %s as the prerelease token", prerelease_token) + logger.info("Forcing use of %s as the prerelease token", prerelease_token) translator.prerelease_token = prerelease_token # Only push if we're committing changes if push_changes and not commit_changes and not create_tag: - log.info("changes will not be pushed because --no-commit disables pushing") + logger.info("changes will not be pushed because --no-commit disables pushing") push_changes &= commit_changes # Only push if we're creating a tag if push_changes and not create_tag and not commit_changes: - log.info("new tag will not be pushed because --no-tag disables pushing") + logger.info("new tag will not be pushed because --no-tag disables pushing") push_changes &= create_tag # Only make a release if we're pushing the changes if make_vcs_release and not push_changes: - log.info("No vcs release will be created because pushing changes is disabled") + logger.info( + "No vcs release will be created because pushing changes is disabled" + ) make_vcs_release &= push_changes if not forced_level_bump: @@ -511,7 +510,7 @@ def version( # noqa: C901 allow_zero_version=runtime.allow_zero_version, ) else: - log.warning( + logger.warning( "Forcing a '%s' release due to '--%s' command-line flag", force_level, ( @@ -665,7 +664,7 @@ def version( # noqa: C901 noop=opts.noop, ) except GitCommitEmptyIndexError: - log.info("No local changes to add to any commit, skipping") + logger.info("No local changes to add to any commit, skipping") # Tag the version after potentially creating a new HEAD commit. # This way if no source code is modified, i.e. all metadata updates @@ -711,7 +710,7 @@ def version( # noqa: C901 return if not isinstance(hvcs_client, RemoteHvcsBase): - log.info("Remote does not support releases. Skipping release creation...") + logger.info("Remote does not support releases. Skipping release creation...") return license_cfg = runtime.project_metadata.get( @@ -774,7 +773,7 @@ def version( # noqa: C901 exception = err finally: if exception is not None: - log.exception(exception) + logger.exception(exception) click.echo(str(exception), err=True) if help_message: click.echo(help_message, err=True) diff --git a/src/semantic_release/cli/config.py b/src/semantic_release/cli/config.py index be8ad4a5e..0ed1eba78 100644 --- a/src/semantic_release/cli/config.py +++ b/src/semantic_release/cli/config.py @@ -54,13 +54,13 @@ NotAReleaseBranch, ParserLoadError, ) +from semantic_release.globals import logger from semantic_release.helpers import dynamic_import from semantic_release.version.declarations.i_version_replacer import IVersionReplacer from semantic_release.version.declarations.pattern import PatternVersionDeclaration from semantic_release.version.declarations.toml import TomlVersionDeclaration from semantic_release.version.translator import VersionTranslator -log = logging.getLogger(__name__) NonEmptyString = Annotated[str, Field(..., min_length=1)] @@ -179,7 +179,7 @@ def validate_match(cls, patterns: Tuple[str, ...]) -> Tuple[str, ...]: @field_validator("changelog_file", mode="after") @classmethod def changelog_file_deprecation_warning(cls, val: str) -> str: - log.warning( + logger.warning( str.join( " ", [ @@ -329,7 +329,7 @@ def check_insecure_flag(self, url_str: str, field_name: str) -> None: ) if scheme == "https" and self.insecure: - log.warning( + logger.warning( str.join( "\n", [ @@ -402,7 +402,7 @@ def verify_git_repo_dir(cls, dir_path: Path) -> Path: @classmethod def tag_commit_parser_deprecation_warning(cls, val: str) -> str: if val == "tag": - log.warning( + logger.warning( str.join( " ", [ @@ -418,7 +418,7 @@ def tag_commit_parser_deprecation_warning(cls, val: str) -> str: @classmethod def angular_commit_parser_deprecation_warning(cls, val: str) -> str: if val == "angular": - log.warning( + logger.warning( str.join( " ", [ @@ -585,14 +585,14 @@ def select_branch_options( ) -> BranchConfig: for group, options in choices.items(): if regexp(options.match).match(active_branch): - log.info( + logger.info( "Using group %r options, as %r matches %r", group, options.match, active_branch, ) return options - log.debug( + logger.debug( "Rejecting group %r as %r doesn't match %r", group, options.match, @@ -774,10 +774,10 @@ def from_raw_config( # noqa: C901 # Provide warnings if the token is missing if not raw.remote.token: - log.debug("hvcs token is not set") + logger.debug("hvcs token is not set") if not raw.remote.ignore_token_for_push: - log.warning("Token value is missing!") + logger.warning("Token value is missing!") # hvcs_client hvcs_client_cls = _known_hvcs[raw.remote.type] diff --git a/src/semantic_release/cli/github_actions_output.py b/src/semantic_release/cli/github_actions_output.py index 253b2419c..7d7782922 100644 --- a/src/semantic_release/cli/github_actions_output.py +++ b/src/semantic_release/cli/github_actions_output.py @@ -1,12 +1,10 @@ from __future__ import annotations -import logging import os +from semantic_release.globals import logger from semantic_release.version.version import Version -log = logging.getLogger(__name__) - class VersionGitHubActionsOutput: OUTPUT_ENV_VAR = "GITHUB_OUTPUT" @@ -71,7 +69,7 @@ def to_output_text(self) -> str: def write_if_possible(self, filename: str | None = None) -> None: output_file = filename or os.getenv(self.OUTPUT_ENV_VAR) if not output_file: - log.info("not writing GitHub Actions output, as no file specified") + logger.info("not writing GitHub Actions output, as no file specified") return with open(output_file, "a", encoding="utf-8") as f: diff --git a/src/semantic_release/cli/masking_filter.py b/src/semantic_release/cli/masking_filter.py index 2c0fdb947..f2e4f825f 100644 --- a/src/semantic_release/cli/masking_filter.py +++ b/src/semantic_release/cli/masking_filter.py @@ -1,16 +1,20 @@ from __future__ import annotations -import logging import re from collections import defaultdict -from typing import Iterable +from logging import Filter as LoggingFilter +from typing import TYPE_CHECKING -log = logging.getLogger(__name__) +from semantic_release.globals import logger + +if TYPE_CHECKING: # pragma: no cover + from logging import LogRecord + from typing import Iterable # https://relaxdiego.com/2014/07/logging-in-python.html # Updated/adapted for Python3 -class MaskingFilter(logging.Filter): +class MaskingFilter(LoggingFilter): REPLACE_STR = "*" * 4 _UNWANTED = frozenset([s for obj in ("", None) for s in (repr(obj), str(obj))]) @@ -27,11 +31,11 @@ def __init__( def add_mask_for(self, data: str, name: str = "redacted") -> MaskingFilter: if data and data not in self._UNWANTED: - log.debug("Adding redact pattern '%r' to redact_patterns", name) + logger.debug("Adding redact pattern '%r' to redact_patterns", name) self._redact_patterns[name].add(data) return self - def filter(self, record: logging.LogRecord) -> bool: + def filter(self, record: LogRecord) -> bool: # Note if we blindly mask all types, we will actually cast arguments to # log functions from external libraries to strings before they are # formatted into the message - for example, a dependency calling @@ -58,7 +62,7 @@ def filter(self, record: logging.LogRecord) -> bool: def mask(self, msg: str) -> str: if not isinstance(msg, str): - log.debug( # type: ignore[unreachable] + logger.debug( # type: ignore[unreachable] "cannot mask object of type %s", type(msg) ) return msg diff --git a/src/semantic_release/cli/util.py b/src/semantic_release/cli/util.py index 97d264b02..0f62d3d10 100644 --- a/src/semantic_release/cli/util.py +++ b/src/semantic_release/cli/util.py @@ -3,7 +3,6 @@ from __future__ import annotations import json -import logging import sys from pathlib import Path from textwrap import dedent, indent @@ -14,8 +13,7 @@ from tomlkit.exceptions import TOMLKitError from semantic_release.errors import InvalidConfiguration - -log = logging.getLogger(__name__) +from semantic_release.globals import logger def rprint(msg: str) -> None: @@ -76,21 +74,21 @@ def load_raw_config_file(config_file: Path | str) -> dict[Any, Any]: This function will also raise FileNotFoundError if it is raised while trying to read the specified configuration file """ - log.info("Loading configuration from %s", config_file) + logger.info("Loading configuration from %s", config_file) raw_text = (Path() / config_file).resolve().read_text(encoding="utf-8") try: - log.debug("Trying to parse configuration %s in TOML format", config_file) + logger.debug("Trying to parse configuration %s in TOML format", config_file) return parse_toml(raw_text) except InvalidConfiguration as e: - log.debug("Configuration %s is invalid TOML: %s", config_file, str(e)) - log.debug("trying to parse %s as JSON", config_file) + logger.debug("Configuration %s is invalid TOML: %s", config_file, str(e)) + logger.debug("trying to parse %s as JSON", config_file) try: # could be a "parse_json" function but it's a one-liner here return json.loads(raw_text)["semantic_release"] except KeyError: # valid configuration, but no "semantic_release" or "tool.semantic_release" # top level key - log.debug( + logger.debug( "configuration has no 'semantic_release' or 'tool.semantic_release' " "top-level key" ) diff --git a/src/semantic_release/commit_parser/angular.py b/src/semantic_release/commit_parser/angular.py index ca739cc91..eeef82796 100644 --- a/src/semantic_release/commit_parser/angular.py +++ b/src/semantic_release/commit_parser/angular.py @@ -5,7 +5,6 @@ from __future__ import annotations -import logging import re from functools import reduce from itertools import zip_longest @@ -31,15 +30,13 @@ ) from semantic_release.enums import LevelBump from semantic_release.errors import InvalidParserOptions +from semantic_release.globals import logger from semantic_release.helpers import sort_numerically, text_reducer if TYPE_CHECKING: # pragma: no cover from git.objects.commit import Commit -logger = logging.getLogger(__name__) - - def _logged_parse_error(commit: Commit, error: str) -> ParseError: logger.debug(error) return ParseError(commit, error=error) diff --git a/src/semantic_release/commit_parser/conventional.py b/src/semantic_release/commit_parser/conventional.py index b4eac746c..3cd50d9c7 100644 --- a/src/semantic_release/commit_parser/conventional.py +++ b/src/semantic_release/commit_parser/conventional.py @@ -3,7 +3,6 @@ import re from functools import reduce from itertools import zip_longest -from logging import getLogger from re import compile as regexp from textwrap import dedent from typing import TYPE_CHECKING, Tuple @@ -26,6 +25,7 @@ ) from semantic_release.enums import LevelBump from semantic_release.errors import InvalidParserOptions +from semantic_release.globals import logger from semantic_release.helpers import sort_numerically, text_reducer if TYPE_CHECKING: # pragma: no cover @@ -33,7 +33,7 @@ def _logged_parse_error(commit: Commit, error: str) -> ParseError: - getLogger(__name__).debug(error) + logger.debug(error) return ParseError(commit, error=error) diff --git a/src/semantic_release/commit_parser/emoji.py b/src/semantic_release/commit_parser/emoji.py index 2199c6948..801160208 100644 --- a/src/semantic_release/commit_parser/emoji.py +++ b/src/semantic_release/commit_parser/emoji.py @@ -2,7 +2,6 @@ from __future__ import annotations -import logging import re from functools import reduce from itertools import zip_longest @@ -27,10 +26,9 @@ ) from semantic_release.enums import LevelBump from semantic_release.errors import InvalidParserOptions +from semantic_release.globals import logger from semantic_release.helpers import sort_numerically, text_reducer -logger = logging.getLogger(__name__) - @dataclass class EmojiParserOptions(ParserOptions): diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index 6083d7359..7e0e6b246 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -49,7 +49,6 @@ import re from functools import reduce from itertools import zip_longest -from logging import getLogger from re import compile as regexp from textwrap import dedent from typing import TYPE_CHECKING, Tuple @@ -71,6 +70,7 @@ ) from semantic_release.enums import LevelBump from semantic_release.errors import InvalidParserOptions +from semantic_release.globals import logger from semantic_release.helpers import sort_numerically, text_reducer if TYPE_CHECKING: # pragma: no cover @@ -78,7 +78,7 @@ def _logged_parse_error(commit: Commit, error: str) -> ParseError: - getLogger(__name__).debug(error) + logger.debug(error) return ParseError(commit, error=error) diff --git a/src/semantic_release/commit_parser/tag.py b/src/semantic_release/commit_parser/tag.py index b9a042cc7..c6c90a936 100644 --- a/src/semantic_release/commit_parser/tag.py +++ b/src/semantic_release/commit_parser/tag.py @@ -2,7 +2,6 @@ from __future__ import annotations -import logging import re from git.objects.commit import Commit @@ -12,8 +11,7 @@ from semantic_release.commit_parser.token import ParsedCommit, ParseError, ParseResult from semantic_release.commit_parser.util import breaking_re, parse_paragraphs from semantic_release.enums import LevelBump - -logger = logging.getLogger(__name__) +from semantic_release.globals import logger re_parser = re.compile(r"(?P[^\n]+)" + r"(:?\n\n(?P.+))?", re.DOTALL) diff --git a/src/semantic_release/gitproject.py b/src/semantic_release/gitproject.py index ef174d85c..0e4592599 100644 --- a/src/semantic_release/gitproject.py +++ b/src/semantic_release/gitproject.py @@ -4,7 +4,6 @@ from contextlib import nullcontext from datetime import datetime -from logging import getLogger from pathlib import Path from typing import TYPE_CHECKING @@ -19,6 +18,7 @@ GitPushError, GitTagError, ) +from semantic_release.globals import logger if TYPE_CHECKING: # pragma: no cover from contextlib import _GeneratorContextManager @@ -36,7 +36,7 @@ def __init__( credential_masker: MaskingFilter | None = None, ) -> None: self._project_root = Path(directory).resolve() - self._logger = getLogger(__name__) + self._logger = logger self._cred_masker = credential_masker or MaskingFilter() self._commit_author = commit_author diff --git a/src/semantic_release/globals.py b/src/semantic_release/globals.py index a0ac61ddb..deb6af987 100644 --- a/src/semantic_release/globals.py +++ b/src/semantic_release/globals.py @@ -2,7 +2,17 @@ from __future__ import annotations +from logging import getLogger +from typing import TYPE_CHECKING + from semantic_release.enums import SemanticReleaseLogLevels +if TYPE_CHECKING: + from logging import Logger + +# GLOBAL VARIABLES log_level: SemanticReleaseLogLevels = SemanticReleaseLogLevels.WARNING """int: Logging level for semantic-release""" + +logger: Logger = getLogger(__package__) +"""Logger for semantic-release""" diff --git a/src/semantic_release/helpers.py b/src/semantic_release/helpers.py index 5f6723ec4..c50369575 100644 --- a/src/semantic_release/helpers.py +++ b/src/semantic_release/helpers.py @@ -1,7 +1,6 @@ from __future__ import annotations import importlib.util -import logging import os import re import string @@ -12,13 +11,14 @@ from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Sequence, TypeVar from urllib.parse import urlsplit +from semantic_release.globals import logger + if TYPE_CHECKING: # pragma: no cover + from logging import Logger from re import Pattern from typing import Iterable -log = logging.getLogger(__name__) - number_pattern = regexp(r"(?P\S*?)(?P\d[\d,]*)\b") hex_number_pattern = regexp( r"(?P\S*?)(?:0x)?(?P[0-9a-f]+)\b", IGNORECASE @@ -118,7 +118,7 @@ def check_tag_format(tag_format: str) -> None: _FuncType = Callable[..., _R] -def logged_function(logger: logging.Logger) -> Callable[[_FuncType[_R]], _FuncType[_R]]: +def logged_function(logger: Logger) -> Callable[[_FuncType[_R]], _FuncType[_R]]: """ Decorator which adds debug logging of a function's input arguments and return value. @@ -151,7 +151,7 @@ def _wrapper(*args: Any, **kwargs: Any) -> _R: return _logged_function -@logged_function(log) +@logged_function(logger) def dynamic_import(import_path: str) -> Any: """ Dynamically import an object from a conventionally formatted "module:attribute" @@ -175,7 +175,7 @@ def dynamic_import(import_path: str) -> Any: ) if module_path not in sys.modules: - log.debug("Loading '%s' from file '%s'", module_path, module_filepath) + logger.debug("Loading '%s' from file '%s'", module_path, module_filepath) spec = importlib.util.spec_from_file_location( module_path, str(module_filepath) ) @@ -190,9 +190,9 @@ def dynamic_import(import_path: str) -> Any: # Otherwise, import as a module try: - log.debug("Importing module '%s'", module_name) + logger.debug("Importing module '%s'", module_name) module = importlib.import_module(module_name) - log.debug("Loading '%s' from module '%s'", attr, module_name) + logger.debug("Loading '%s' from module '%s'", attr, module_name) return getattr(module, attr) except TypeError as err: raise ImportError( @@ -242,7 +242,7 @@ def parse_git_url(url: str) -> ParsedGitUrl: Raises ValueError if the url can't be parsed. """ - log.debug("Parsing git url %r", url) + logger.debug("Parsing git url %r", url) # Normalizers are a list of tuples of (pattern, replacement) normalizers = [ diff --git a/src/semantic_release/hvcs/_base.py b/src/semantic_release/hvcs/_base.py index 60c6a5f87..fc6668dcd 100644 --- a/src/semantic_release/hvcs/_base.py +++ b/src/semantic_release/hvcs/_base.py @@ -2,7 +2,6 @@ from __future__ import annotations -import logging import warnings from abc import ABCMeta, abstractmethod from functools import lru_cache @@ -14,10 +13,6 @@ from typing import Any, Callable -# Globals -logger = logging.getLogger(__name__) - - class HvcsBase(metaclass=ABCMeta): """ Interface for subclasses interacting with a remote vcs environment diff --git a/src/semantic_release/hvcs/bitbucket.py b/src/semantic_release/hvcs/bitbucket.py index e0f9e8656..08c2aa6be 100644 --- a/src/semantic_release/hvcs/bitbucket.py +++ b/src/semantic_release/hvcs/bitbucket.py @@ -5,7 +5,6 @@ from __future__ import annotations -import logging import os from functools import lru_cache from pathlib import PurePosixPath @@ -14,16 +13,13 @@ from urllib3.util.url import Url, parse_url +from semantic_release.globals import logger from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase if TYPE_CHECKING: # pragma: no cover from typing import Any, Callable -# Globals -log = logging.getLogger(__name__) - - class Bitbucket(RemoteHvcsBase): """ Bitbucket HVCS interface for interacting with BitBucket repositories @@ -161,7 +157,7 @@ def _derive_api_url_from_base_domain(self) -> Url: def _get_repository_owner_and_name(self) -> tuple[str, str]: # ref: https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ if "BITBUCKET_REPO_FULL_NAME" in os.environ: - log.info("Getting repository owner and name from environment variables.") + logger.info("Getting repository owner and name from environment variables.") owner, name = os.environ["BITBUCKET_REPO_FULL_NAME"].rsplit("/", 1) return owner, name diff --git a/src/semantic_release/hvcs/gitea.py b/src/semantic_release/hvcs/gitea.py index c8e241122..994c2459c 100644 --- a/src/semantic_release/hvcs/gitea.py +++ b/src/semantic_release/hvcs/gitea.py @@ -3,7 +3,6 @@ from __future__ import annotations import glob -import logging import os from pathlib import PurePosixPath from re import compile as regexp @@ -18,6 +17,7 @@ IncompleteReleaseError, UnexpectedResponse, ) +from semantic_release.globals import logger from semantic_release.helpers import logged_function from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase from semantic_release.hvcs.token_auth import TokenAuth @@ -27,10 +27,6 @@ from typing import Any, Callable -# Globals -log = logging.getLogger(__name__) - - class Gitea(RemoteHvcsBase): """Gitea helper class""" @@ -82,7 +78,7 @@ def __init__( allow_insecure=allow_insecure, ) - @logged_function(log) + @logged_function(logger) def create_release( self, tag: str, @@ -126,7 +122,7 @@ def create_release( ) return -1 - log.info("Creating release for tag %s", tag) + logger.info("Creating release for tag %s", tag) releases_endpoint = self.create_api_url( endpoint=f"/repos/{self.owner}/{self.repo_name}/releases", ) @@ -146,7 +142,7 @@ def create_release( try: release_id: int = response.json()["id"] - log.info("Successfully created release with ID: %s", release_id) + logger.info("Successfully created release with ID: %s", release_id) except JSONDecodeError as err: raise UnexpectedResponse("Unreadable json response") from err except KeyError as err: @@ -154,7 +150,7 @@ def create_release( errors = [] for asset in assets or []: - log.info("Uploading asset %s", asset) + logger.info("Uploading asset %s", asset) try: self.upload_release_asset(release_id, asset) except HTTPError as err: @@ -168,13 +164,13 @@ def create_release( return release_id for error in errors: - log.exception(error) + logger.exception(error) raise IncompleteReleaseError( f"Failed to upload asset{'s' if len(errors) > 1 else ''} to release!" ) - @logged_function(log) + @logged_function(logger) @suppress_not_found def get_release_id_by_tag(self, tag: str) -> int | None: """ @@ -200,7 +196,7 @@ def get_release_id_by_tag(self, tag: str) -> int | None: except KeyError as err: raise UnexpectedResponse("JSON response is missing an id") from err - @logged_function(log) + @logged_function(logger) def edit_release_notes(self, release_id: int, release_notes: str) -> int: """ Edit a release with updated change notes @@ -210,7 +206,7 @@ def edit_release_notes(self, release_id: int, release_notes: str) -> int: :return: The ID of the release that was edited """ - log.info("Updating release %s", release_id) + logger.info("Updating release %s", release_id) release_endpoint = self.create_api_url( endpoint=f"/repos/{self.owner}/{self.repo_name}/releases/{release_id}", ) @@ -225,7 +221,7 @@ def edit_release_notes(self, release_id: int, release_notes: str) -> int: return release_id - @logged_function(log) + @logged_function(logger) def create_or_update_release( self, tag: str, release_notes: str, prerelease: bool = False ) -> int: @@ -236,12 +232,12 @@ def create_or_update_release( :return: The status of the request """ - log.info("Creating release for %s", tag) + logger.info("Creating release for %s", tag) try: return self.create_release(tag, release_notes, prerelease) except HTTPError as err: - log.debug("error creating release: %s", err) - log.debug("looking for an existing release to update") + logger.debug("error creating release: %s", err) + logger.debug("looking for an existing release to update") release_id = self.get_release_id_by_tag(tag) if release_id is None: @@ -250,10 +246,10 @@ def create_or_update_release( ) # If this errors we let it die - log.debug("Found existing release %s, updating", release_id) + logger.debug("Found existing release %s, updating", release_id) return self.edit_release_notes(release_id, release_notes) - @logged_function(log) + @logged_function(logger) def asset_upload_url(self, release_id: str) -> str: """ Get the correct upload url for a release @@ -264,7 +260,7 @@ def asset_upload_url(self, release_id: str) -> str: endpoint=f"/repos/{self.owner}/{self.repo_name}/releases/{release_id}/assets", ) - @logged_function(log) + @logged_function(logger) def upload_release_asset( self, release_id: int, @@ -301,7 +297,7 @@ def upload_release_asset( # Raise an error if the request was not successful response.raise_for_status() - log.info( + logger.info( "Successfully uploaded %s to Gitea, url: %s, status code: %s", file, response.url, @@ -310,7 +306,7 @@ def upload_release_asset( return True - @logged_function(log) + @logged_function(logger) def upload_dists(self, tag: str, dist_glob: str) -> int: """ Upload distributions to a release @@ -322,7 +318,7 @@ def upload_dists(self, tag: str, dist_glob: str) -> int: # Find the release corresponding to this tag release_id = self.get_release_id_by_tag(tag=tag) if not release_id: - log.warning("No release corresponds to tag %s, can't upload dists", tag) + logger.warning("No release corresponds to tag %s, can't upload dists", tag) return 0 # Upload assets @@ -334,7 +330,7 @@ def upload_dists(self, tag: str, dist_glob: str) -> int: self.upload_release_asset(release_id, file_path) n_succeeded += 1 except HTTPError: # noqa: PERF203 - log.exception("error uploading asset %s", file_path) + logger.exception("error uploading asset %s", file_path) return n_succeeded diff --git a/src/semantic_release/hvcs/github.py b/src/semantic_release/hvcs/github.py index 016643267..5ccc9004b 100644 --- a/src/semantic_release/hvcs/github.py +++ b/src/semantic_release/hvcs/github.py @@ -3,7 +3,6 @@ from __future__ import annotations import glob -import logging import mimetypes import os from functools import lru_cache @@ -20,6 +19,7 @@ IncompleteReleaseError, UnexpectedResponse, ) +from semantic_release.globals import logger from semantic_release.helpers import logged_function from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase from semantic_release.hvcs.token_auth import TokenAuth @@ -29,10 +29,6 @@ from typing import Any, Callable -# Globals -log = logging.getLogger(__name__) - - # Add a mime type for wheels # Fix incorrect entries in the `mimetypes` registry. # On Windows, the Python standard library's `mimetypes` reads in @@ -200,13 +196,13 @@ def _derive_api_url_from_base_domain(self) -> Url: def _get_repository_owner_and_name(self) -> tuple[str, str]: # Github actions context if "GITHUB_REPOSITORY" in os.environ: - log.debug("getting repository owner and name from environment variables") + logger.debug("getting repository owner and name from environment variables") owner, name = os.environ["GITHUB_REPOSITORY"].rsplit("/", 1) return owner, name return super()._get_repository_owner_and_name() - @logged_function(log) + @logged_function(logger) def create_release( self, tag: str, @@ -253,7 +249,7 @@ def create_release( ) return -1 - log.info("Creating release for tag %s", tag) + logger.info("Creating release for tag %s", tag) releases_endpoint = self.create_api_url( endpoint=f"/repos/{self.owner}/{self.repo_name}/releases", ) @@ -273,7 +269,7 @@ def create_release( try: release_id: int = response.json()["id"] - log.info("Successfully created release with ID: %s", release_id) + logger.info("Successfully created release with ID: %s", release_id) except JSONDecodeError as err: raise UnexpectedResponse("Unreadable json response") from err except KeyError as err: @@ -281,7 +277,7 @@ def create_release( errors = [] for asset in assets or []: - log.info("Uploading asset %s", asset) + logger.info("Uploading asset %s", asset) try: self.upload_release_asset(release_id, asset) except HTTPError as err: @@ -295,13 +291,13 @@ def create_release( return release_id for error in errors: - log.exception(error) + logger.exception(error) raise IncompleteReleaseError( f"Failed to upload asset{'s' if len(errors) > 1 else ''} to release!" ) - @logged_function(log) + @logged_function(logger) @suppress_not_found def get_release_id_by_tag(self, tag: str) -> int | None: """ @@ -326,7 +322,7 @@ def get_release_id_by_tag(self, tag: str) -> int | None: except KeyError as err: raise UnexpectedResponse("JSON response is missing an id") from err - @logged_function(log) + @logged_function(logger) def edit_release_notes(self, release_id: int, release_notes: str) -> int: """ Edit a release with updated change notes @@ -335,7 +331,7 @@ def edit_release_notes(self, release_id: int, release_notes: str) -> int: :param release_notes: The release notes for this version :return: The ID of the release that was edited """ - log.info("Updating release %s", release_id) + logger.info("Updating release %s", release_id) release_endpoint = self.create_api_url( endpoint=f"/repos/{self.owner}/{self.repo_name}/releases/{release_id}", ) @@ -350,7 +346,7 @@ def edit_release_notes(self, release_id: int, release_notes: str) -> int: return release_id - @logged_function(log) + @logged_function(logger) def create_or_update_release( self, tag: str, release_notes: str, prerelease: bool = False ) -> int: @@ -361,12 +357,12 @@ def create_or_update_release( :param prerelease: Whether or not this release should be created as a prerelease :return: The status of the request """ - log.info("Creating release for %s", tag) + logger.info("Creating release for %s", tag) try: return self.create_release(tag, release_notes, prerelease) except HTTPError as err: - log.debug("error creating release: %s", err) - log.debug("looking for an existing release to update") + logger.debug("error creating release: %s", err) + logger.debug("looking for an existing release to update") release_id = self.get_release_id_by_tag(tag) if release_id is None: @@ -374,11 +370,11 @@ def create_or_update_release( f"release id for tag {tag} not found, and could not be created" ) - log.debug("Found existing release %s, updating", release_id) + logger.debug("Found existing release %s, updating", release_id) # If this errors we let it die return self.edit_release_notes(release_id, release_notes) - @logged_function(log) + @logged_function(logger) @suppress_not_found def asset_upload_url(self, release_id: str) -> str | None: """ @@ -405,7 +401,7 @@ def asset_upload_url(self, release_id: str) -> str | None: "JSON response is missing a key 'upload_url'" ) from err - @logged_function(log) + @logged_function(logger) def upload_release_asset( self, release_id: int, file: str, label: str | None = None ) -> bool: @@ -442,7 +438,7 @@ def upload_release_asset( # Raise an error if the upload was unsuccessful response.raise_for_status() - log.debug( + logger.debug( "Successfully uploaded %s to Github, url: %s, status code: %s", file, response.url, @@ -451,7 +447,7 @@ def upload_release_asset( return True - @logged_function(log) + @logged_function(logger) def upload_dists(self, tag: str, dist_glob: str) -> int: """ Upload distributions to a release @@ -462,7 +458,7 @@ def upload_dists(self, tag: str, dist_glob: str) -> int: # Find the release corresponding to this version release_id = self.get_release_id_by_tag(tag=tag) if not release_id: - log.warning("No release corresponds to tag %s, can't upload dists", tag) + logger.warning("No release corresponds to tag %s, can't upload dists", tag) return 0 # Upload assets @@ -474,14 +470,14 @@ def upload_dists(self, tag: str, dist_glob: str) -> int: self.upload_release_asset(release_id, file_path) n_succeeded += 1 except HTTPError: # noqa: PERF203 - log.exception("error uploading asset %s", file_path) + logger.exception("error uploading asset %s", file_path) return n_succeeded def remote_url(self, use_token: bool = True) -> str: """Get the remote url including the token for authentication if requested""" if not (self.token and use_token): - log.info("requested to use token for push but no token set, ignoring...") + logger.info("requested to use token for push but no token set, ignoring...") return self._remote_url actor = os.getenv("GITHUB_ACTOR", None) diff --git a/src/semantic_release/hvcs/gitlab.py b/src/semantic_release/hvcs/gitlab.py index 67b8e7512..198d22e00 100644 --- a/src/semantic_release/hvcs/gitlab.py +++ b/src/semantic_release/hvcs/gitlab.py @@ -2,7 +2,6 @@ from __future__ import annotations -import logging import os from functools import lru_cache from pathlib import PurePosixPath @@ -17,6 +16,7 @@ from semantic_release.cli.util import noop_report from semantic_release.errors import UnexpectedResponse +from semantic_release.globals import logger from semantic_release.helpers import logged_function from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase from semantic_release.hvcs.util import suppress_not_found @@ -27,13 +27,6 @@ from gitlab.v4.objects import Project as GitLabProject -log = logging.getLogger(__name__) - - -# Globals -log = logging.getLogger(__name__) - - class Gitlab(RemoteHvcsBase): """Gitlab HVCS interface for interacting with Gitlab repositories""" @@ -91,12 +84,12 @@ def _get_repository_owner_and_name(self) -> tuple[str, str]: available, otherwise from parsing the remote url """ if "CI_PROJECT_NAMESPACE" in os.environ and "CI_PROJECT_NAME" in os.environ: - log.debug("getting repository owner and name from environment variables") + logger.debug("getting repository owner and name from environment variables") return os.environ["CI_PROJECT_NAMESPACE"], os.environ["CI_PROJECT_NAME"] return super()._get_repository_owner_and_name() - @logged_function(log) + @logged_function(logger) def create_release( self, tag: str, @@ -112,7 +105,7 @@ def create_release( :param release_notes: The changelog description for this version only :param prerelease: This parameter has no effect in GitLab :param assets: A list of paths to files to upload as assets (TODO: not implemented) - :param noop: If True, do not perform any actions, only log intents + :param noop: If True, do not perform any actions, only logger intents :return: The tag of the release @@ -123,7 +116,7 @@ def create_release( noop_report(f"would have created a release for tag {tag}") return tag - log.info("Creating release for %s", tag) + logger.info("Creating release for %s", tag) # ref: https://docs.gitlab.com/ee/api/releases/index.html#create-a-release self.project.releases.create( { @@ -133,10 +126,10 @@ def create_release( "description": release_notes, } ) - log.info("Successfully created release for %s", tag) + logger.info("Successfully created release for %s", tag) return tag - @logged_function(log) + @logged_function(logger) @suppress_not_found def get_release_by_tag(self, tag: str) -> gitlab.v4.objects.ProjectRelease | None: """ @@ -151,12 +144,12 @@ def get_release_by_tag(self, tag: str) -> gitlab.v4.objects.ProjectRelease | Non try: return self.project.releases.get(tag) except gitlab.exceptions.GitlabGetError: - log.debug("Release %s not found", tag) + logger.debug("Release %s not found", tag) return None except KeyError as err: raise UnexpectedResponse("JSON response is missing commit.id") from err - @logged_function(log) + @logged_function(logger) def edit_release_notes( # type: ignore[override] self, release: gitlab.v4.objects.ProjectRelease, @@ -174,7 +167,7 @@ def edit_release_notes( # type: ignore[override] :raises: GitlabUpdateError: If the server cannot perform the request """ - log.info( + logger.info( "Updating release %s [%s]", release.name, release.attributes.get("commit", {}).get("id"), @@ -183,7 +176,7 @@ def edit_release_notes( # type: ignore[override] release.save() return str(release.get_id()) - @logged_function(log) + @logged_function(logger) def create_or_update_release( self, tag: str, release_notes: str, prerelease: bool = False ) -> str: @@ -205,7 +198,7 @@ def create_or_update_release( tag=tag, release_notes=release_notes, prerelease=prerelease ) except gitlab.GitlabCreateError: - log.info( + logger.info( "New release %s could not be created for project %s", tag, self.project_namespace, @@ -216,7 +209,7 @@ def create_or_update_release( f"release for tag {tag} could not be found, and could not be created" ) - log.debug( + logger.debug( "Found existing release commit %s, updating", release_obj.commit.get("id") ) # If this errors we let it die diff --git a/src/semantic_release/hvcs/remote_hvcs_base.py b/src/semantic_release/hvcs/remote_hvcs_base.py index d26b01881..14e7a5e2f 100644 --- a/src/semantic_release/hvcs/remote_hvcs_base.py +++ b/src/semantic_release/hvcs/remote_hvcs_base.py @@ -2,7 +2,6 @@ from __future__ import annotations -import logging from abc import ABCMeta, abstractmethod from pathlib import PurePosixPath from typing import TYPE_CHECKING @@ -15,10 +14,6 @@ from typing import Any -# Globals -logger = logging.getLogger(__name__) - - class RemoteHvcsBase(HvcsBase, metaclass=ABCMeta): """ Interface for subclasses interacting with a remote VCS diff --git a/src/semantic_release/hvcs/util.py b/src/semantic_release/hvcs/util.py index f54e08b6b..e125cf638 100644 --- a/src/semantic_release/hvcs/util.py +++ b/src/semantic_release/hvcs/util.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging from functools import wraps from typing import TYPE_CHECKING, Any, Callable, TypeVar @@ -8,11 +7,11 @@ from requests.adapters import HTTPAdapter from requests.packages.urllib3.util.retry import Retry # type: ignore[import] +from semantic_release.globals import logger + if TYPE_CHECKING: # pragma: no cover from semantic_release.hvcs.token_auth import TokenAuth -logger = logging.getLogger(__name__) - def build_requests_session( raise_for_status: bool = True, diff --git a/src/semantic_release/version/algorithm.py b/src/semantic_release/version/algorithm.py index c738e6581..506848cb1 100644 --- a/src/semantic_release/version/algorithm.py +++ b/src/semantic_release/version/algorithm.py @@ -11,6 +11,7 @@ from semantic_release.const import DEFAULT_VERSION from semantic_release.enums import LevelBump, SemanticReleaseLogLevels from semantic_release.errors import InternalError, InvalidVersion +from semantic_release.globals import logger from semantic_release.helpers import validate_types_in_sequence if TYPE_CHECKING: # pragma: no cover @@ -29,9 +30,6 @@ from semantic_release.version.version import Version -logger = logging.getLogger(__name__) - - def tags_and_versions( tags: Iterable[Tag], translator: VersionTranslator ) -> list[tuple[Tag, Version]]: diff --git a/src/semantic_release/version/declaration.py b/src/semantic_release/version/declaration.py index 3c225d1b5..e0400f3df 100644 --- a/src/semantic_release/version/declaration.py +++ b/src/semantic_release/version/declaration.py @@ -1,13 +1,13 @@ from __future__ import annotations -# TODO: Remove v10 +# TODO: Remove v11 from abc import ABC, abstractmethod -from logging import getLogger from pathlib import Path from typing import TYPE_CHECKING from deprecated.sphinx import deprecated +from semantic_release.globals import logger from semantic_release.version.declarations.enum import VersionStampType from semantic_release.version.declarations.i_version_replacer import IVersionReplacer from semantic_release.version.declarations.pattern import PatternVersionDeclaration @@ -25,7 +25,6 @@ "TomlVersionDeclaration", "VersionDeclarationABC", ] -log = getLogger(__name__) @deprecated( @@ -58,7 +57,7 @@ def content(self) -> str: is cached in the instance variable _content """ if self._content is None: - log.debug( + logger.debug( "No content stored, reading from source file %s", self.path.resolve() ) self._content = self.path.read_text() @@ -66,7 +65,7 @@ def content(self) -> str: @content.deleter def content(self) -> None: - log.debug("resetting instance-stored source file contents") + logger.debug("resetting instance-stored source file contents") self._content = None @abstractmethod @@ -102,6 +101,6 @@ def write(self, content: str) -> None: >>> vd = MyVD("path", r"__version__ = (?P\d+\d+\d+)") >>> vd.write(vd.replace(new_version)) """ - log.debug("writing content to %r", self.path.resolve()) + logger.debug("writing content to %r", self.path.resolve()) self.path.write_text(content) self._content = None diff --git a/src/semantic_release/version/declarations/pattern.py b/src/semantic_release/version/declarations/pattern.py index e96234117..f08c208a4 100644 --- a/src/semantic_release/version/declarations/pattern.py +++ b/src/semantic_release/version/declarations/pattern.py @@ -1,6 +1,5 @@ from __future__ import annotations -from logging import getLogger from pathlib import Path from re import ( MULTILINE, @@ -14,6 +13,7 @@ from semantic_release.cli.util import noop_report from semantic_release.const import SEMVER_REGEX +from semantic_release.globals import logger from semantic_release.version.declarations.enum import VersionStampType from semantic_release.version.declarations.i_version_replacer import IVersionReplacer from semantic_release.version.version import Version @@ -22,9 +22,6 @@ from re import Match -log = getLogger(__name__) - - class VersionSwapper: """Callable to replace a version number in a string with a new version number.""" @@ -78,7 +75,7 @@ def __init__( def content(self) -> str: """A cached property that stores the content of the configured source file.""" if self._content is None: - log.debug("No content stored, reading from source file %s", self._path) + logger.debug("No content stored, reading from source file %s", self._path) if not self._path.exists(): raise FileNotFoundError(f"path {self._path!r} does not exist") @@ -109,7 +106,7 @@ def parse(self) -> set[Version]: # pragma: no cover for m in self._search_pattern.finditer(self.content) } - log.debug( + logger.debug( "Parsing current version: path=%r pattern=%r num_matches=%s", self._path.resolve(), self._search_pattern, @@ -136,7 +133,7 @@ def replace(self, new_version: Version) -> str: self.content, ) - log.debug( + logger.debug( "path=%r pattern=%r num_matches=%r", self._path, self._search_pattern, diff --git a/src/semantic_release/version/declarations/toml.py b/src/semantic_release/version/declarations/toml.py index ed9542870..59e6996ac 100644 --- a/src/semantic_release/version/declarations/toml.py +++ b/src/semantic_release/version/declarations/toml.py @@ -1,6 +1,5 @@ from __future__ import annotations -from logging import getLogger from pathlib import Path from typing import Any, Dict, cast @@ -9,13 +8,11 @@ from dotty_dict import Dotty from semantic_release.cli.util import noop_report +from semantic_release.globals import logger from semantic_release.version.declarations.enum import VersionStampType from semantic_release.version.declarations.i_version_replacer import IVersionReplacer from semantic_release.version.version import Version -# globals -log = getLogger(__name__) - class TomlVersionDeclaration(IVersionReplacer): def __init__( @@ -30,7 +27,7 @@ def __init__( def content(self) -> str: """A cached property that stores the content of the configured source file.""" if self._content is None: - log.debug("No content stored, reading from source file %s", self._path) + logger.debug("No content stored, reading from source file %s", self._path) if not self._path.exists(): raise FileNotFoundError(f"path {self._path!r} does not exist") @@ -52,7 +49,7 @@ def parse(self) -> set[Version]: # pragma: no cover content = self._load() maybe_version: str = content.get(self._search_text) # type: ignore[return-value] if maybe_version is not None: - log.debug( + logger.debug( "Found a key %r that looks like a version (%r)", self._search_text, maybe_version, @@ -69,7 +66,7 @@ def replace(self, new_version: Version) -> str: """ content = self._load() if self._search_text in content: - log.info( + logger.info( "found %r in source file contents, replacing with %s", self._search_text, new_version, diff --git a/src/semantic_release/version/translator.py b/src/semantic_release/version/translator.py index 7a4ce275f..6340701da 100644 --- a/src/semantic_release/version/translator.py +++ b/src/semantic_release/version/translator.py @@ -1,14 +1,12 @@ from __future__ import annotations -import logging import re from semantic_release.const import SEMVER_REGEX +from semantic_release.globals import logger from semantic_release.helpers import check_tag_format from semantic_release.version.version import Version -log = logging.getLogger(__name__) - class VersionTranslator: """ @@ -37,7 +35,7 @@ def _invert_tag_format_to_re(cls, tag_format: str) -> re.Pattern[str]: tag_format.replace(r"{version}", r"(?P.*)"), flags=re.VERBOSE, ) - log.debug("inverted tag_format %r to %r", tag_format, pat.pattern) + logger.debug("inverted tag_format %r to %r", tag_format, pat.pattern) return pat def __init__( diff --git a/src/semantic_release/version/version.py b/src/semantic_release/version/version.py index 41ec5e107..032596e4a 100644 --- a/src/semantic_release/version/version.py +++ b/src/semantic_release/version/version.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging import re from functools import wraps from itertools import zip_longest @@ -9,11 +8,9 @@ from semantic_release.const import SEMVER_REGEX from semantic_release.enums import LevelBump from semantic_release.errors import InvalidVersion +from semantic_release.globals import logger from semantic_release.helpers import check_tag_format -log = logging.getLogger(__name__) - - # Very heavily inspired by semver.version:_comparator, I don't think there's # a cleaner way to do this # https://github.com/python-semver/python-semver/blob/b5317af9a7e99e6a86df98320e73be72d5adf0de/src/semver/version.py#L32 @@ -116,7 +113,7 @@ def parse( if not isinstance(version_str, str): raise InvalidVersion(f"{version_str!r} cannot be parsed as a Version") - log.debug("attempting to parse string %r as Version", version_str) + logger.debug("attempting to parse string %r as Version", version_str) match = cls._VERSION_REGEX.fullmatch(version_str) if not match: raise InvalidVersion(f"{version_str!r} is not a valid Version") @@ -131,7 +128,7 @@ def parse( r"'1.2.3-my-custom-3rc.4'." ) prerelease_token, prerelease_revision = pm.groups() - log.debug( + logger.debug( "parsed prerelease_token %s, prerelease_revision %s from version " "string %s", prerelease_token, @@ -140,10 +137,10 @@ def parse( ) else: prerelease_revision = None - log.debug("version string %s parsed as a non-prerelease", version_str) + logger.debug("version string %s parsed as a non-prerelease", version_str) build_metadata = match.group("buildmetadata") or "" - log.debug( + logger.debug( "parsed build metadata %r from version string %s", build_metadata, version_str, @@ -218,7 +215,7 @@ def bump(self, level: LevelBump) -> Version: if type(level) != LevelBump: raise TypeError(f"Unexpected level {level!r}: expected {LevelBump!r}") - log.debug("performing a %s level bump", level) + logger.debug("performing a %s level bump", level) if level is LevelBump.MAJOR: return Version( self.major + 1, From d53bfac1ed4bb610387d80b99f68b540f7e4cb99 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 02:12:40 -0600 Subject: [PATCH 071/120] test(cmd-version): update test cases for updated emoji version values --- tests/const.py | 2 +- tests/e2e/cmd_version/test_version.py | 4 +++- tests/e2e/cmd_version/test_version_print.py | 10 +++++----- tests/e2e/cmd_version/test_version_strict.py | 4 +++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/const.py b/tests/const.py index 88f5677eb..41df4533d 100644 --- a/tests/const.py +++ b/tests/const.py @@ -92,7 +92,7 @@ class RepoActionStep(str, Enum): *EMOJI_COMMITS_PATCH, ":sparkles::pencil: docs for something special\n", # Emoji in description should not be used to evaluate change type - ":sparkles: last minute rush order\n\n:boom: Good thing we're 10x developers\n", + ":sparkles: last minute rush order\n\nGood thing we're 10x developers :boom:\n", ) EMOJI_COMMITS_MAJOR = ( *EMOJI_COMMITS_MINOR, diff --git a/tests/e2e/cmd_version/test_version.py b/tests/e2e/cmd_version/test_version.py index f892c3cf6..39ce8eb7e 100644 --- a/tests/e2e/cmd_version/test_version.py +++ b/tests/e2e/cmd_version/test_version.py @@ -7,6 +7,8 @@ import pytest from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture +from semantic_release.hvcs.github import Github + from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -217,7 +219,7 @@ def test_version_on_last_release( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD] - result = run_cli(cli_cmd[1:]) + result = run_cli(cli_cmd[1:], env={Github.DEFAULT_ENV_TOKEN_NAME: "1234"}) # take measurement after running the version command repo_status_after = repo.git.status(short=True) diff --git a/tests/e2e/cmd_version/test_version_print.py b/tests/e2e/cmd_version/test_version_print.py index b3afc2fc3..71c41ccca 100644 --- a/tests/e2e/cmd_version/test_version_print.py +++ b/tests/e2e/cmd_version/test_version_print.py @@ -5,6 +5,8 @@ import pytest from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture +from semantic_release.hvcs.github import Github + from tests.const import ( MAIN_PROG_NAME, VERSION_SUBCMD, @@ -126,7 +128,7 @@ def test_version_print_next_version( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print", *force_args] - result = run_cli(cli_cmd[1:]) + result = run_cli(cli_cmd[1:], env={Github.DEFAULT_ENV_TOKEN_NAME: "1234"}) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -136,7 +138,6 @@ def test_version_print_next_version( # Evaluate assert_successful_exit_code(result, cli_cmd) - assert not result.stderr assert f"{next_release_version}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -296,7 +297,7 @@ def test_version_print_tag_prints_next_tag( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-tag", *force_args] - result = run_cli(cli_cmd[1:]) + result = run_cli(cli_cmd[1:], env={Github.DEFAULT_ENV_TOKEN_NAME: "1234"}) # take measurement after running the version command repo_status_after = repo.git.status(short=True) @@ -306,7 +307,6 @@ def test_version_print_tag_prints_next_tag( # Evaluate assert_successful_exit_code(result, cli_cmd) - assert not result.stderr assert f"{next_release_tag}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -340,7 +340,7 @@ def test_version_print_last_released_prints_version( # Act cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released"] - result = run_cli(cli_cmd[1:]) + result = run_cli(cli_cmd[1:], env={Github.DEFAULT_ENV_TOKEN_NAME: "1234"}) # take measurement after running the version command repo_status_after = repo.git.status(short=True) diff --git a/tests/e2e/cmd_version/test_version_strict.py b/tests/e2e/cmd_version/test_version_strict.py index a0ff9bb8d..951a9966f 100644 --- a/tests/e2e/cmd_version/test_version_strict.py +++ b/tests/e2e/cmd_version/test_version_strict.py @@ -5,6 +5,8 @@ import pytest from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture +from semantic_release.hvcs.github import Github + from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD from tests.fixtures.repos import repo_w_trunk_only_conventional_commits from tests.util import assert_exit_code @@ -48,7 +50,7 @@ def test_version_already_released_when_strict( # Act cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD] - result = run_cli(cli_cmd[1:]) + result = run_cli(cli_cmd[1:], env={Github.DEFAULT_ENV_TOKEN_NAME: "1234"}) # take measurement after running the version command repo_status_after = repo.git.status(short=True) From 6a866567c13dfbbdd0d7be5fde8927574c5a3e5f Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 03:03:16 -0600 Subject: [PATCH 072/120] style: fix spelling in comments --- .../e2e/cmd_version/test_version_changelog_custom_commit_msg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/cmd_version/test_version_changelog_custom_commit_msg.py b/tests/e2e/cmd_version/test_version_changelog_custom_commit_msg.py index 180e7c0f4..acff3e728 100644 --- a/tests/e2e/cmd_version/test_version_changelog_custom_commit_msg.py +++ b/tests/e2e/cmd_version/test_version_changelog_custom_commit_msg.py @@ -99,7 +99,7 @@ class Commit2SectionCommit(TypedDict): ) for commit_msg in [ dedent( - # Conventional compliant prefix with skip-ci idicator + # Conventional compliant prefix with skip-ci indicator """\ chore(release): v{version} [skip ci] From c08b835287d74d3995edde790f31afad59e44a43 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 03:05:11 -0600 Subject: [PATCH 073/120] refactor(config): update custom `commit_message` regex converter to match trimmed commits --- src/semantic_release/cli/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/semantic_release/cli/config.py b/src/semantic_release/cli/config.py index 0ed1eba78..1badfe37b 100644 --- a/src/semantic_release/cli/config.py +++ b/src/semantic_release/cli/config.py @@ -718,7 +718,7 @@ def from_raw_config( # noqa: C901 # TODO: add any other placeholders here ), # We use re.escape to ensure that the commit message is treated as a literal - regex_escape(raw.commit_message), + regex_escape(raw.commit_message.strip()), ) ) changelog_excluded_commit_patterns = ( From e69e46c9f9ef24b683752b4d75ddae50356b95db Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 03:27:52 -0600 Subject: [PATCH 074/120] refactor(cmd-version): add `USERNAME` as passed variable to build env on Windows --- src/semantic_release/cli/commands/version.py | 1 + tests/e2e/cmd_version/test_version_build.py | 60 +------------------- 2 files changed, 4 insertions(+), 57 deletions(-) diff --git a/src/semantic_release/cli/commands/version.py b/src/semantic_release/cli/commands/version.py index 66b42c4cb..8a8cf94a1 100644 --- a/src/semantic_release/cli/commands/version.py +++ b/src/semantic_release/cli/commands/version.py @@ -228,6 +228,7 @@ def get_windows_env() -> Mapping[str, str | None]: "SYSTEMROOT", "TEMP", "TMP", + "USERNAME", # must include for python getpass.getuser() on windows "USERPROFILE", "USERSID", "WINDIR", diff --git a/tests/e2e/cmd_version/test_version_build.py b/tests/e2e/cmd_version/test_version_build.py index 1145ce627..390882f9e 100644 --- a/tests/e2e/cmd_version/test_version_build.py +++ b/tests/e2e/cmd_version/test_version_build.py @@ -151,6 +151,7 @@ def test_version_runs_build_command_windows( update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: mock.MagicMock, post_mocker: mock.Mock, + clean_os_environment: dict[str, str], ): if shell == "cmd": build_result_file = get_wheel_file("%NEW_VERSION%") @@ -174,9 +175,8 @@ def test_version_runs_build_command_windows( ) build_command = pyproject_config.get("tool.semantic_release.build_command", "") patched_os_environment = { + **clean_os_environment, "CI": "true", - "PATH": os.getenv("PATH", ""), - "HOME": "/home/username", "VIRTUAL_ENV": "./.venv", # Simulate that all CI's are set "GITHUB_ACTIONS": "true", @@ -184,29 +184,6 @@ def test_version_runs_build_command_windows( "GITEA_ACTIONS": "true", "BITBUCKET_REPO_FULL_NAME": "python-semantic-release/python-semantic-release.git", "PSR_DOCKER_GITHUB_ACTION": "true", - # Windows - "ALLUSERSAPPDATA": "C:\\ProgramData", - "ALLUSERSPROFILE": "C:\\ProgramData", - "APPDATA": "C:\\Users\\Username\\AppData\\Roaming", - "COMMONPROGRAMFILES": "C:\\Program Files\\Common Files", - "COMMONPROGRAMFILES(X86)": "C:\\Program Files (x86)\\Common Files", - "DEFAULTUSERPROFILE": "C:\\Users\\Default", - "HOMEPATH": "\\Users\\Username", - "PATHEXT": ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC", - "PROFILESFOLDER": "C:\\Users", - "PROGRAMFILES": "C:\\Program Files", - "PROGRAMFILES(X86)": "C:\\Program Files (x86)", - "SYSTEM": "C:\\Windows\\System32", - "SYSTEM16": "C:\\Windows\\System16", - "SYSTEM32": "C:\\Windows\\System32", - "SYSTEMDRIVE": "C:", - "SYSTEMROOT": "C:\\Windows", - "TEMP": "C:\\Users\\Username\\AppData\\Local\\Temp", - "TMP": "C:\\Users\\Username\\AppData\\Local\\Temp", - "USERPROFILE": "C:\\Users\\Username", - "USERSID": "S-1-5-21-1234567890-123456789-123456789-1234", - "USERNAME": "Username", # must include for python getpass.getuser() on windows - "WINDIR": "C:\\Windows", } # Wrap subprocess.run to capture the arguments to the call @@ -226,42 +203,17 @@ def test_version_runs_build_command_windows( [shell, "/c" if shell == "cmd" else "-Command", build_command], check=True, env={ + **clean_os_environment, "NEW_VERSION": next_release_version, # injected into environment "CI": patched_os_environment["CI"], "BITBUCKET_CI": "true", # Converted "GITHUB_ACTIONS": patched_os_environment["GITHUB_ACTIONS"], "GITEA_ACTIONS": patched_os_environment["GITEA_ACTIONS"], "GITLAB_CI": patched_os_environment["GITLAB_CI"], - "HOME": patched_os_environment["HOME"], - "PATH": patched_os_environment["PATH"], "VIRTUAL_ENV": patched_os_environment["VIRTUAL_ENV"], "PSR_DOCKER_GITHUB_ACTION": patched_os_environment[ "PSR_DOCKER_GITHUB_ACTION" ], - # Windows - "ALLUSERSAPPDATA": patched_os_environment["ALLUSERSAPPDATA"], - "ALLUSERSPROFILE": patched_os_environment["ALLUSERSPROFILE"], - "APPDATA": patched_os_environment["APPDATA"], - "COMMONPROGRAMFILES": patched_os_environment["COMMONPROGRAMFILES"], - "COMMONPROGRAMFILES(X86)": patched_os_environment[ - "COMMONPROGRAMFILES(X86)" - ], - "DEFAULTUSERPROFILE": patched_os_environment["DEFAULTUSERPROFILE"], - "HOMEPATH": patched_os_environment["HOMEPATH"], - "PATHEXT": patched_os_environment["PATHEXT"], - "PROFILESFOLDER": patched_os_environment["PROFILESFOLDER"], - "PROGRAMFILES": patched_os_environment["PROGRAMFILES"], - "PROGRAMFILES(X86)": patched_os_environment["PROGRAMFILES(X86)"], - "SYSTEM": patched_os_environment["SYSTEM"], - "SYSTEM16": patched_os_environment["SYSTEM16"], - "SYSTEM32": patched_os_environment["SYSTEM32"], - "SYSTEMDRIVE": patched_os_environment["SYSTEMDRIVE"], - "SYSTEMROOT": patched_os_environment["SYSTEMROOT"], - "TEMP": patched_os_environment["TEMP"], - "TMP": patched_os_environment["TMP"], - "USERPROFILE": patched_os_environment["USERPROFILE"], - "USERSID": patched_os_environment["USERSID"], - "WINDIR": patched_os_environment["WINDIR"], }, ) @@ -294,11 +246,7 @@ def test_version_runs_build_command_w_user_env( patched_os_environment = { **clean_os_environment, "CI": "true", - "PATH": os.getenv("PATH", ""), - "HOME": "/home/username", "VIRTUAL_ENV": "./.venv", - # Windows - "USERNAME": "Username", # must include for python getpass.getuser() on windows # Simulate that all CI's are set "GITHUB_ACTIONS": "true", "GITLAB_CI": "true", @@ -366,8 +314,6 @@ def test_version_runs_build_command_w_user_env( "GITHUB_ACTIONS": patched_os_environment["GITHUB_ACTIONS"], "GITEA_ACTIONS": patched_os_environment["GITEA_ACTIONS"], "GITLAB_CI": patched_os_environment["GITLAB_CI"], - "HOME": patched_os_environment["HOME"], - "PATH": patched_os_environment["PATH"], "VIRTUAL_ENV": patched_os_environment["VIRTUAL_ENV"], "PSR_DOCKER_GITHUB_ACTION": patched_os_environment[ "PSR_DOCKER_GITHUB_ACTION" From c6b6eabbfe100d2c741620eb3fa12a382531fa94 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 20:44:26 -0600 Subject: [PATCH 075/120] refactor(config)!: change `allow_zero_version` default to `false` Changes the default behavior of PSR when the `allow_zero_version` setting is not provided. BREAKING CHANGE: This release switches the `allow_zero_version` default to `false`. This change is to encourage less `0.x` releases as the default but rather allow the experienced developer to choose when `0.x` is appropriate. There are way too many projects in the ecosystems that never leave `0.x` and that is problematic for the industry tools that help auto-update based on SemVer. We should strive for publishing usable tools and maintaining good forethought for when compatibility must break. If your configuration already sets the `allow_zero_version` value, this change will have no effect on your project. If you want to use `0.x` versions, from the start then change `allow_zero_version` to `true` in your configuration. --- src/semantic_release/cli/config.py | 2 +- src/semantic_release/version/algorithm.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/semantic_release/cli/config.py b/src/semantic_release/cli/config.py index 1badfe37b..4253e09a3 100644 --- a/src/semantic_release/cli/config.py +++ b/src/semantic_release/cli/config.py @@ -360,7 +360,7 @@ class RawConfig(BaseModel): commit_parser_options: Dict[str, Any] = {} logging_use_named_masks: bool = False major_on_zero: bool = True - allow_zero_version: bool = True + allow_zero_version: bool = False repo_dir: Annotated[Path, Field(validate_default=True)] = Path(".") remote: RemoteConfig = RemoteConfig() no_git_verify: bool = False diff --git a/src/semantic_release/version/algorithm.py b/src/semantic_release/version/algorithm.py index 506848cb1..fa24e3fa1 100644 --- a/src/semantic_release/version/algorithm.py +++ b/src/semantic_release/version/algorithm.py @@ -245,9 +245,9 @@ def next_version( repo: Repo, translator: VersionTranslator, commit_parser: CommitParser[ParseResult, ParserOptions], + allow_zero_version: bool, + major_on_zero: bool, prerelease: bool = False, - major_on_zero: bool = True, - allow_zero_version: bool = True, ) -> Version: """ Evaluate the history within `repo`, and based on the tags and commits in the repo From 203d29d9d6b8e862eabe2f99dbd27eabf04e75e2 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 20:48:43 -0600 Subject: [PATCH 076/120] docs(configuration): change default value for `allow_zero_version` in the description --- docs/configuration.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index aa904dc9d..979d6e2ab 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -142,7 +142,9 @@ version to be ``1.0.0``, regardless of patch, minor, or major change level. Additionally, when ``allow_zero_version`` is set to ``false``, the :ref:`config-major_on_zero` setting is ignored. -**Default:** ``true`` +*Default changed to ``false`` in $NEW_VERSION* + +**Default:** ``false`` ---- From 7f6899ea8c1eb34868fb0da385c99c857e508213 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Tue, 20 May 2025 19:23:09 -0600 Subject: [PATCH 077/120] test(cmd-version): update tests to handle new default `allow_zero_version=False` --- tests/e2e/cmd_version/test_version.py | 4 +- tests/e2e/cmd_version/test_version_bump.py | 5 +- .../e2e/cmd_version/test_version_changelog.py | 2 +- tests/e2e/cmd_version/test_version_print.py | 119 +++++++++++++++++- .../cmd_version/test_version_release_notes.py | 4 +- tests/e2e/cmd_version/test_version_stamp.py | 14 +-- .../repos/trunk_based_dev/repo_w_no_tags.py | 42 +++++++ 7 files changed, 175 insertions(+), 15 deletions(-) diff --git a/tests/e2e/cmd_version/test_version.py b/tests/e2e/cmd_version/test_version.py index 39ce8eb7e..5cb9700cf 100644 --- a/tests/e2e/cmd_version/test_version.py +++ b/tests/e2e/cmd_version/test_version.py @@ -35,7 +35,7 @@ "repo_result, next_release_version", # must use a repo that is ready for a release to prevent no release # logic from being triggered before the noop logic - [(lazy_fixture(repo_w_no_tags_conventional_commits.__name__), "0.1.0")], + [(lazy_fixture(repo_w_no_tags_conventional_commits.__name__), "1.0.0")], ) def test_version_noop_is_noop( repo_result: BuiltRepoResult, @@ -273,7 +273,7 @@ def test_version_only_tag_push( # Assert only tag was created, it was pushed and then release was created assert_successful_exit_code(result, cli_cmd) - assert tag_after == "v0.1.0" + assert tag_after == "v1.0.0" assert head_before == head_after assert mocked_git_push.call_count == 1 # 0 for commit, 1 for tag assert post_mocker.call_count == 1 diff --git a/tests/e2e/cmd_version/test_version_bump.py b/tests/e2e/cmd_version/test_version_bump.py index 1faa10cb2..c08efb9ee 100644 --- a/tests/e2e/cmd_version/test_version_bump.py +++ b/tests/e2e/cmd_version/test_version_bump.py @@ -34,6 +34,7 @@ repo_w_github_flow_w_feature_release_channel_conventional_commits, repo_w_initial_commit, repo_w_no_tags_conventional_commits, + repo_w_no_tags_conventional_commits_w_zero_version, repo_w_no_tags_emoji_commits, repo_w_no_tags_scipy_commits, repo_w_trunk_only_conventional_commits, @@ -69,7 +70,9 @@ [ *( ( - lazy_fixture(repo_w_no_tags_conventional_commits.__name__), + lazy_fixture( + repo_w_no_tags_conventional_commits_w_zero_version.__name__ + ), cli_args, next_release_version, ) diff --git a/tests/e2e/cmd_version/test_version_changelog.py b/tests/e2e/cmd_version/test_version_changelog.py index 212e6110e..ae60e6638 100644 --- a/tests/e2e/cmd_version/test_version_changelog.py +++ b/tests/e2e/cmd_version/test_version_changelog.py @@ -340,7 +340,7 @@ def test_version_updates_changelog_wo_prev_releases( str(changelog_file.name), ) - version = "v0.1.0" + version = "v1.0.0" rst_version_header = f"{version} ({repo_build_date_str})" search_n_replacements = { ChangelogOutputFormat.MARKDOWN: ( diff --git a/tests/e2e/cmd_version/test_version_print.py b/tests/e2e/cmd_version/test_version_print.py index 71c41ccca..4efd1e02f 100644 --- a/tests/e2e/cmd_version/test_version_print.py +++ b/tests/e2e/cmd_version/test_version_print.py @@ -21,6 +21,7 @@ ) from tests.fixtures.repos.trunk_based_dev.repo_w_no_tags import ( repo_w_no_tags_conventional_commits_using_tag_format, + repo_w_no_tags_conventional_commits_w_zero_version, ) from tests.util import ( add_text_to_file, @@ -212,8 +213,7 @@ def test_version_print_next_version( marks=pytest.mark.comprehensive, ) for repo_fixture_name in ( - repo_w_no_tags_conventional_commits.__name__, - repo_w_no_tags_conventional_commits_using_tag_format.__name__, + repo_w_no_tags_conventional_commits_w_zero_version.__name__, ) for cli_args, next_release_version in ( # Dynamic version bump determination (based on commits) @@ -317,6 +317,121 @@ def test_version_print_tag_prints_next_tag( assert post_mocker.call_count == 0 +@pytest.mark.parametrize( + "repo_result, commits, force_args, next_release_version", + [ + pytest.param( + lazy_fixture(repo_fixture_name), + [], + cli_args, + next_release_version, + marks=pytest.mark.comprehensive, + ) + for repo_fixture_name in ( + repo_w_no_tags_conventional_commits.__name__, + repo_w_no_tags_conventional_commits_using_tag_format.__name__, + ) + for cli_args, next_release_version in ( + # Dynamic version bump determination (based on commits) + ([], "1.0.0"), + # Dynamic version bump determination (based on commits) with build metadata + (["--build-metadata", "build.12345"], "1.0.0+build.12345"), + # Forced version bump + (["--prerelease"], "0.0.0-rc.1"), + (["--patch"], "0.0.1"), + (["--minor"], "0.1.0"), + (["--major"], "1.0.0"), + # Forced version bump with --build-metadata + (["--patch", "--build-metadata", "build.12345"], "0.0.1+build.12345"), + # Forced version bump with --as-prerelease + (["--prerelease", "--as-prerelease"], "0.0.0-rc.1"), + (["--patch", "--as-prerelease"], "0.0.1-rc.1"), + (["--minor", "--as-prerelease"], "0.1.0-rc.1"), + (["--major", "--as-prerelease"], "1.0.0-rc.1"), + # Forced version bump with --as-prerelease and modified --prerelease-token + ( + ["--patch", "--as-prerelease", "--prerelease-token", "beta"], + "0.0.1-beta.1", + ), + # Forced version bump with --as-prerelease and modified --prerelease-token + # and --build-metadata + ( + [ + "--patch", + "--as-prerelease", + "--prerelease-token", + "beta", + "--build-metadata", + "build.12345", + ], + "0.0.1-beta.1+build.12345", + ), + ) + ], +) +def test_version_print_tag_prints_next_tag_no_zero_versions( + repo_result: BuiltRepoResult, + commits: list[str], + force_args: list[str], + next_release_version: str, + get_cfg_value_from_def: GetCfgValueFromDefFn, + file_in_repo: str, + run_cli: RunCliFn, + mocked_git_push: MagicMock, + post_mocker: Mocker, +): + """ + Given a generic repository at the latest release version and a subsequent commit, + When running the version command with the --print-tag flag, + Then the expected next release tag should be printed and exit without + making any changes to the repository. + + Note: The point of this test is to only verify that the `--print-tag` flag does not + make any changes to the repository--not to validate if the next version is calculated + correctly per the repository structure (see test_version_release & + test_version_force_level for correctness). + + However, we do validate that --print-tag & a force option and/or --as-prerelease options + work together to print the next release tag correctly but not make a change to the repo. + """ + repo = repo_result["repo"] + repo_def = repo_result["definition"] + tag_format_str: str = get_cfg_value_from_def(repo_def, "tag_format_str") # type: ignore[assignment] + next_release_tag = tag_format_str.format(version=next_release_version) + + if len(commits) > 1: + # Make a commit to ensure we have something to release + # otherwise the "no release will be made" logic will kick in first + add_text_to_file(repo, file_in_repo) + repo.git.commit(m=commits[-1], a=True) + + # Setup: take measurement before running the version command + repo_status_before = repo.git.status(short=True) + head_before = repo.head.commit.hexsha + tags_before = {tag.name for tag in repo.tags} + + # Act + cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-tag", *force_args] + result = run_cli(cli_cmd[1:], env={Github.DEFAULT_ENV_TOKEN_NAME: "1234"}) + + # take measurement after running the version command + repo_status_after = repo.git.status(short=True) + head_after = repo.head.commit.hexsha + tags_after = {tag.name for tag in repo.tags} + tags_set_difference = set.difference(tags_after, tags_before) + + # Evaluate + assert_successful_exit_code(result, cli_cmd) + assert f"{next_release_tag}\n" == result.stdout + + # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) + assert repo_status_before == repo_status_after + assert head_before == head_after + assert not tags_set_difference + assert mocked_git_push.call_count == 0 + assert post_mocker.call_count == 0 + + @pytest.mark.parametrize( "repo_result", [lazy_fixture(repo_w_trunk_only_conventional_commits.__name__)], diff --git a/tests/e2e/cmd_version/test_version_release_notes.py b/tests/e2e/cmd_version/test_version_release_notes.py index 6786e1d3e..2b434f29c 100644 --- a/tests/e2e/cmd_version/test_version_release_notes.py +++ b/tests/e2e/cmd_version/test_version_release_notes.py @@ -46,7 +46,7 @@ @pytest.mark.parametrize( "repo_result, next_release_version", [ - (lazy_fixture(repo_w_no_tags_conventional_commits.__name__), "0.1.0"), + (lazy_fixture(repo_w_no_tags_conventional_commits.__name__), "1.0.0"), ], ) def test_custom_release_notes_template( @@ -131,7 +131,7 @@ def test_default_release_notes_license_statement( get_hvcs_client_from_repo_def: GetHvcsClientFromRepoDefFn, generate_default_release_notes_from_def: GenerateDefaultReleaseNotesFromDefFn, ): - new_version = "0.1.0" + new_version = "1.0.0" # Setup now_datetime = stable_now_date() diff --git a/tests/e2e/cmd_version/test_version_stamp.py b/tests/e2e/cmd_version/test_version_stamp.py index bad09d23b..a12059f37 100644 --- a/tests/e2e/cmd_version/test_version_stamp.py +++ b/tests/e2e/cmd_version/test_version_stamp.py @@ -147,7 +147,7 @@ def test_stamp_version_variables_python( update_pyproject_toml: UpdatePyprojectTomlFn, example_project_dir: ExProjectDir, ) -> None: - new_version = "0.1.0" + new_version = "1.0.0" target_file = example_project_dir.joinpath( "src", EXAMPLE_PROJECT_NAME, "_version.py" ) @@ -181,7 +181,7 @@ def test_stamp_version_toml( default_tag_format_str: str, ) -> None: orig_version = "0.0.0" - new_version = "0.1.0" + new_version = "1.0.0" orig_release = default_tag_format_str.format(version=orig_version) new_release = default_tag_format_str.format(version=new_version) target_file = Path("example.toml") @@ -236,7 +236,7 @@ def test_stamp_version_variables_yaml( update_pyproject_toml: UpdatePyprojectTomlFn, ) -> None: orig_version = "0.0.0" - new_version = "0.1.0" + new_version = "1.0.0" target_file = Path("example.yml") orig_yaml = dedent( f"""\ @@ -286,7 +286,7 @@ def test_stamp_version_variables_yaml_cff( Based on https://github.com/python-semantic-release/python-semantic-release/issues/962 """ orig_version = "0.0.0" - new_version = "0.1.0" + new_version = "1.0.0" target_file = Path("CITATION.cff") orig_yaml = dedent( f"""\ @@ -335,7 +335,7 @@ def test_stamp_version_variables_json( update_pyproject_toml: UpdatePyprojectTomlFn, ) -> None: orig_version = "0.0.0" - new_version = "0.1.0" + new_version = "1.0.0" target_file = Path("plugins.json") orig_json = { "id": "test-plugin", @@ -385,7 +385,7 @@ def test_stamp_version_variables_yaml_github_actions( Based on https://github.com/python-semantic-release/python-semantic-release/issues/1156 """ orig_version = "0.0.0" - new_version = "0.1.0" + new_version = "1.0.0" target_file = Path("combined.yml") action1_yaml_filepath = "my-org/my-actions/.github/workflows/action1.yml" action2_yaml_filepath = "my-org/my-actions/.github/workflows/action2.yml" @@ -457,7 +457,7 @@ def test_stamp_version_variables_yaml_kustomization_container_spec( Based on https://github.com/python-semantic-release/python-semantic-release/issues/846 """ orig_version = "0.0.0" - new_version = "0.1.0" + new_version = "1.0.0" target_file = Path("kustomization.yaml") orig_yaml = dedent( f"""\ diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py index cdb5c2afc..b8134d0d3 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py @@ -259,6 +259,48 @@ def _build_repo(cached_repo_path: Path) -> Sequence[RepoActions]: } +@pytest.fixture +def repo_w_no_tags_conventional_commits_w_zero_version( + build_repo_from_definition: BuildRepoFromDefinitionFn, + get_repo_definition_4_trunk_only_repo_w_no_tags: GetRepoDefinitionFn, + get_cached_repo_data: GetCachedRepoDataFn, + build_repo_or_copy_cache: BuildRepoOrCopyCacheFn, + build_spec_hash_4_repo_w_no_tags: str, + example_project_git_repo: ExProjectGitRepoFn, + example_project_dir: ExProjectDir, + change_to_ex_proj_dir: None, +) -> BuiltRepoResult: + """Replicates repo with no tags, but with allow_zero_version=True""" + repo_name = repo_w_no_tags_conventional_commits_w_zero_version.__name__ + commit_type: CommitConvention = ( + repo_name.split("_commits", maxsplit=1)[0].split("_")[-1] # type: ignore[assignment] + ) + + def _build_repo(cached_repo_path: Path) -> Sequence[RepoActions]: + repo_construction_steps = get_repo_definition_4_trunk_only_repo_w_no_tags( + commit_type=commit_type, + extra_configs={ + "tool.semantic_release.allow_zero_version": True, + }, + ) + return build_repo_from_definition(cached_repo_path, repo_construction_steps) + + build_repo_or_copy_cache( + repo_name=repo_name, + build_spec_hash=build_spec_hash_4_repo_w_no_tags, + build_repo_func=_build_repo, + dest_dir=example_project_dir, + ) + + if not (cached_repo_data := get_cached_repo_data(proj_dirname=repo_name)): + raise ValueError("Failed to retrieve repo data from cache") + + return { + "definition": cached_repo_data["build_definition"], + "repo": example_project_git_repo(), + } + + @pytest.fixture def repo_w_no_tags_conventional_commits( build_trunk_only_repo_w_no_tags: BuildSpecificRepoFn, From 0e114c3458a24b87bfd2d6b0cd3f5cfdc9497084 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 21:14:32 -0600 Subject: [PATCH 078/120] refactor(config)!: change `changelog.default_templates.mask_initial_release` default to `true` Changes the default behavior of PSR when `mask_initial_release` setting is not provided. BREAKING CHANGE: This release switches the `changelog.default_templates.mask_initial_release` default to `true`. This change is intended to toggle better recommended outputs of the default changelog. Conceptually, the very first release is hard to describe--one can only provide new features as nothing exists yet for the end user. No changelog should be written as there is no start point to compare the "changes" to. The recommendation instead is to only list a simple message as `Initial Release`. This is now the default for PSR when providing the very first release (no pre-existing tags) in the changelog and release notes. If your configuration already sets the `changelog.default_templates.mask_initial_release` value, then this change will have no effect on your project. If you do NOT want to mask the first release information, then set `changelog.default_templates.mask_initial_release` to `false` in your configuration. --- src/semantic_release/cli/config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/semantic_release/cli/config.py b/src/semantic_release/cli/config.py index 4253e09a3..ce40ca0b9 100644 --- a/src/semantic_release/cli/config.py +++ b/src/semantic_release/cli/config.py @@ -128,8 +128,7 @@ class ChangelogEnvironmentConfig(BaseModel): class DefaultChangelogTemplatesConfig(BaseModel): changelog_file: str = "CHANGELOG.md" output_format: ChangelogOutputFormat = ChangelogOutputFormat.NONE - # TODO: Breaking Change v10, it will become True - mask_initial_release: bool = False + mask_initial_release: bool = True @model_validator(mode="after") def interpret_output_format(self) -> Self: From 5fb02ab6e3b8278ecbf92ed35083ffb595bc19b8 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 21:18:53 -0600 Subject: [PATCH 079/120] docs(configuration): change the default for the base changelog's `mask_initial_release` value --- docs/configuration.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 979d6e2ab..b4b9b2b23 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -390,7 +390,9 @@ is there to document? The message details can be found in the ``first_release.md.j2`` and ``first_release.rst.j2`` templates of the default changelog template directory. -**Default:** ``false`` +*Default changed to ``true`` in $NEW_VERSION.* + +**Default:** ``true`` .. seealso:: - :ref:`changelog-templates-default_changelog` From 06be5fc138b04ff40579ac0ee45f3d603c55c557 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 19 May 2025 21:20:03 -0600 Subject: [PATCH 080/120] test(fixtures): update repo builder's default parameter value for `mask_initial_release` --- .../e2e/cmd_version/test_version_changelog.py | 146 ++++++++++++++++++ .../cmd_version/test_version_release_notes.py | 13 +- tests/fixtures/git_repo.py | 18 +-- .../git_flow/repo_w_1_release_channel.py | 2 +- .../git_flow/repo_w_2_release_channels.py | 2 +- .../git_flow/repo_w_3_release_channels.py | 2 +- .../git_flow/repo_w_4_release_channels.py | 2 +- .../github_flow/repo_w_default_release.py | 2 +- .../github_flow/repo_w_release_channels.py | 2 +- tests/fixtures/repos/repo_initial_commit.py | 2 +- .../repo_w_dual_version_support.py | 2 +- ...po_w_dual_version_support_w_prereleases.py | 2 +- .../repos/trunk_based_dev/repo_w_no_tags.py | 44 +++++- .../trunk_based_dev/repo_w_prereleases.py | 2 +- .../repos/trunk_based_dev/repo_w_tags.py | 2 +- 15 files changed, 219 insertions(+), 24 deletions(-) diff --git a/tests/e2e/cmd_version/test_version_changelog.py b/tests/e2e/cmd_version/test_version_changelog.py index ae60e6638..6dea5139b 100644 --- a/tests/e2e/cmd_version/test_version_changelog.py +++ b/tests/e2e/cmd_version/test_version_changelog.py @@ -33,6 +33,7 @@ repo_w_github_flow_w_feature_release_channel_emoji_commits, repo_w_github_flow_w_feature_release_channel_scipy_commits, repo_w_no_tags_conventional_commits, + repo_w_no_tags_conventional_commits_unmasked_initial_release, repo_w_no_tags_emoji_commits, repo_w_no_tags_scipy_commits, repo_w_trunk_only_conventional_commits, @@ -311,6 +312,151 @@ def test_version_updates_changelog_wo_prev_releases( insertion_flag: str, stable_now_date: GetStableDateNowFn, format_date_str: FormatDateStrFn, +): + """ + Given the repository has no releases and the user has provided a initialized changelog, + When the version command is run with changelog.mode set to "update", + Then the version is created and the changelog file is updated with only an initial release statement + """ + if not (repo_build_data := cache.get(cache_key, None)): + pytest.fail("Repo build date not found in cache") + + repo_build_datetime = datetime.strptime(repo_build_data["build_date"], "%Y-%m-%d") + now_datetime = stable_now_date().replace( + year=repo_build_datetime.year, + month=repo_build_datetime.month, + day=repo_build_datetime.day, + ) + repo_build_date_str = format_date_str(now_datetime) + + # Custom text to maintain (must be different from the default) + custom_text = "---{ls}{ls}Custom footer text{ls}".format(ls=os.linesep) + + # Set the project configurations + update_pyproject_toml( + "tool.semantic_release.changelog.mode", ChangelogMode.UPDATE.value + ) + update_pyproject_toml( + "tool.semantic_release.changelog.default_templates.changelog_file", + str(changelog_file.name), + ) + + version = "v1.0.0" + rst_version_header = f"{version} ({repo_build_date_str})" + txt_after_insertion_flag = { + ChangelogOutputFormat.MARKDOWN: str.join( + os.linesep, + [ + f"## {version} ({repo_build_date_str})", + "", + "- Initial Release", + ], + ), + ChangelogOutputFormat.RESTRUCTURED_TEXT: str.join( + os.linesep, + [ + f".. _changelog-{version}:", + "", + rst_version_header, + f"{'=' * len(rst_version_header)}", + "", + "* Initial Release", + ], + ), + } + + # Capture and modify the current changelog content to become the expected output + # We much use os.linesep here since the insertion flag is os-specific + with changelog_file.open(newline=os.linesep) as rfd: + initial_changelog_parts = rfd.read().split(insertion_flag) + + # content is os-specific because of the insertion flag & how we read the original file + expected_changelog_content = str.join( + insertion_flag, + [ + initial_changelog_parts[0], + str.join( + os.linesep, + [ + os.linesep, + txt_after_insertion_flag[changelog_format], + "", + custom_text, + ], + ), + ], + ) + + # Grab the Unreleased changelog & create the initialized user changelog + # force output to not perform any newline translations + with changelog_file.open(mode="w", newline="") as wfd: + wfd.write( + str.join( + insertion_flag, + [initial_changelog_parts[0], f"{os.linesep * 2}{custom_text}"], + ) + ) + wfd.flush() + + # Act + with freeze_time(now_datetime.astimezone(timezone.utc)): + cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-push", "--changelog"] + result = run_cli(cli_cmd[1:]) + + # Evaluate + assert_successful_exit_code(result, cli_cmd) + + # Ensure changelog exists + assert changelog_file.exists() + + # Capture the new changelog content (os aware because of expected content) + with changelog_file.open(newline=os.linesep) as rfd: + actual_content = rfd.read() + + # Check that the changelog footer is maintained and updated with Unreleased info + assert expected_changelog_content == actual_content + + +@pytest.mark.parametrize( + "changelog_format, changelog_file, insertion_flag", + [ + ( + ChangelogOutputFormat.MARKDOWN, + lazy_fixture(example_changelog_md.__name__), + lazy_fixture(default_md_changelog_insertion_flag.__name__), + ), + ( + ChangelogOutputFormat.RESTRUCTURED_TEXT, + lazy_fixture(example_changelog_rst.__name__), + lazy_fixture(default_rst_changelog_insertion_flag.__name__), + ), + ], +) +@pytest.mark.parametrize( + "repo_result, cache_key", + [ + pytest.param( + lazy_fixture(repo_fixture), + f"psr/repos/{repo_fixture}", + marks=pytest.mark.comprehensive, + ) + for repo_fixture in [ + # Must not have a single release/tag + repo_w_no_tags_conventional_commits_unmasked_initial_release.__name__, + ] + ], +) +def test_version_updates_changelog_wo_prev_releases_n_unmasked_initial_release( + repo_result: BuiltRepoResult, + cache_key: str, + cache: pytest.Cache, + run_cli: RunCliFn, + update_pyproject_toml: UpdatePyprojectTomlFn, + changelog_format: ChangelogOutputFormat, + changelog_file: Path, + insertion_flag: str, + stable_now_date: GetStableDateNowFn, + format_date_str: FormatDateStrFn, ): """ Given the repository has no releases and the user has provided a initialized changelog, diff --git a/tests/e2e/cmd_version/test_version_release_notes.py b/tests/e2e/cmd_version/test_version_release_notes.py index 2b434f29c..e21059335 100644 --- a/tests/e2e/cmd_version/test_version_release_notes.py +++ b/tests/e2e/cmd_version/test_version_release_notes.py @@ -98,14 +98,16 @@ def test_custom_release_notes_template( @pytest.mark.parametrize( - "repo_result, license_name, license_setting", + "repo_result, license_name, license_setting, mask_initial_release", [ pytest.param( lazy_fixture(repo_fixture_name), license_name, license_setting, + mask_initial_release, marks=pytest.mark.comprehensive, ) + for mask_initial_release in [True, False] for license_name in ["", "MIT", "GPL-3.0"] for license_setting in [ "project.license-expression", @@ -124,6 +126,7 @@ def test_default_release_notes_license_statement( run_cli: RunCliFn, license_name: str, license_setting: str, + mask_initial_release: bool, update_pyproject_toml: UpdatePyprojectTomlFn, mocked_git_push: MagicMock, post_mocker: Mocker, @@ -151,12 +154,18 @@ def test_default_release_notes_license_statement( # Setup: set the license for the test update_pyproject_toml(license_setting, license_name) + # Setup: set mask_initial_release value in configuration + update_pyproject_toml( + "tool.semantic_release.changelog.default_templates.mask_initial_release", + mask_initial_release, + ) + expected_release_notes = generate_default_release_notes_from_def( version_actions=repo_def, hvcs=get_hvcs_client_from_repo_def(repo_def), previous_version=None, license_name=license_name, - mask_initial_release=False, + mask_initial_release=mask_initial_release, ) # Act diff --git a/tests/fixtures/git_repo.py b/tests/fixtures/git_repo.py index 60d0b4bad..106bc55e4 100644 --- a/tests/fixtures/git_repo.py +++ b/tests/fixtures/git_repo.py @@ -126,7 +126,7 @@ def __call__( hvcs_domain: str = ..., tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, # Default as of v10 ) -> tuple[Path, HvcsBase]: ... class CommitNReturnChangelogEntryFn(Protocol): @@ -184,7 +184,7 @@ def __call__( dest_file: Path | None = None, max_version: str | None = None, output_format: ChangelogOutputFormat = ChangelogOutputFormat.MARKDOWN, - mask_initial_release: bool = False, + mask_initial_release: bool = True, # Default as of v10 ) -> str: ... class FormatGitSquashCommitMsgFn(Protocol): @@ -343,7 +343,7 @@ def __call__( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = ..., ignore_merge_commits: bool = True, # Default as of v10 ) -> Sequence[RepoActions]: ... @@ -405,7 +405,7 @@ def __call__( previous_version: Version | None = None, license_name: str = "", dest_file: Path | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, # Default as of v10 ) -> str: ... class GetHvcsClientFromRepoDefFn(Protocol): @@ -1000,7 +1000,7 @@ def _build_configured_base_repo( # noqa: C901 hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, # Default as of v10 ) -> tuple[Path, HvcsBase]: if not cached_example_git_project.exists(): raise RuntimeError("Unable to find cached git project files!") @@ -1259,7 +1259,7 @@ def _build_repo_from_definition( # noqa: C901, its required and its just test c repo_dir = Path(dest_dir) hvcs: Github | Gitlab | Gitea | Bitbucket tag_format_str: str - mask_initial_release: bool = False + mask_initial_release: bool = True # Default as of v10 current_commits: list[CommitDef] = [] current_repo_def: RepoDefinition = {} @@ -1878,8 +1878,7 @@ def _mimic_semantic_release_default_changelog( dest_file: Path | None = None, max_version: str | None = None, output_format: ChangelogOutputFormat = ChangelogOutputFormat.MARKDOWN, - # TODO: Breaking v10, when default is toggled to true, also change this to True - mask_initial_release: bool = False, + mask_initial_release: bool = True, # Default as of v10 ) -> str: if output_format == ChangelogOutputFormat.MARKDOWN: header = dedent( @@ -2094,8 +2093,7 @@ def _generate_default_release_notes( previous_version: Version | None = None, license_name: str = "", dest_file: Path | None = None, - # TODO: Breaking v10, when default is toggled to true, also change this to True - mask_initial_release: bool = False, + mask_initial_release: bool = True, # Default as of v10 ) -> str: limited_repo_def: RepoDefinition = get_commits_from_repo_build_def( build_definition=version_actions, diff --git a/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py b/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py index 9a1669888..bdaf43d64 100644 --- a/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py +++ b/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py @@ -102,7 +102,7 @@ def _get_repo_from_defintion( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() diff --git a/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py index 9d5863a25..537ef4b02 100644 --- a/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py @@ -102,7 +102,7 @@ def _get_repo_from_defintion( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() diff --git a/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py index 1e7f2e04d..bf49b27b5 100644 --- a/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py @@ -104,7 +104,7 @@ def _get_repo_from_defintion( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() diff --git a/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py index 2c8d24e5f..9062bd2b1 100644 --- a/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py @@ -114,7 +114,7 @@ def _get_repo_from_defintion( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() diff --git a/tests/fixtures/repos/github_flow/repo_w_default_release.py b/tests/fixtures/repos/github_flow/repo_w_default_release.py index b0c2da302..25d30a271 100644 --- a/tests/fixtures/repos/github_flow/repo_w_default_release.py +++ b/tests/fixtures/repos/github_flow/repo_w_default_release.py @@ -97,7 +97,7 @@ def _get_repo_from_definition( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() diff --git a/tests/fixtures/repos/github_flow/repo_w_release_channels.py b/tests/fixtures/repos/github_flow/repo_w_release_channels.py index 4549ffbf6..eec636dd4 100644 --- a/tests/fixtures/repos/github_flow/repo_w_release_channels.py +++ b/tests/fixtures/repos/github_flow/repo_w_release_channels.py @@ -97,7 +97,7 @@ def _get_repo_from_defintion( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() diff --git a/tests/fixtures/repos/repo_initial_commit.py b/tests/fixtures/repos/repo_initial_commit.py index 817386ba7..7bc1fc3bb 100644 --- a/tests/fixtures/repos/repo_initial_commit.py +++ b/tests/fixtures/repos/repo_initial_commit.py @@ -77,7 +77,7 @@ def _get_repo_from_definition( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: repo_construction_steps: list[RepoActions] = [] diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py index 83665399c..f909c584b 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py @@ -91,7 +91,7 @@ def _get_repo_from_definition( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py index 7f57d6e01..88c07d6c3 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py @@ -91,7 +91,7 @@ def _get_repo_from_definition( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py index b8134d0d3..37d9b0450 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py @@ -84,7 +84,7 @@ def _get_repo_from_definition( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() @@ -301,6 +301,48 @@ def _build_repo(cached_repo_path: Path) -> Sequence[RepoActions]: } +@pytest.fixture +def repo_w_no_tags_conventional_commits_unmasked_initial_release( + build_repo_from_definition: BuildRepoFromDefinitionFn, + get_repo_definition_4_trunk_only_repo_w_no_tags: GetRepoDefinitionFn, + get_cached_repo_data: GetCachedRepoDataFn, + build_repo_or_copy_cache: BuildRepoOrCopyCacheFn, + build_spec_hash_4_repo_w_no_tags: str, + example_project_git_repo: ExProjectGitRepoFn, + example_project_dir: ExProjectDir, + change_to_ex_proj_dir: None, +) -> BuiltRepoResult: + """Replicates repo with no tags, but with allow_zero_version=True""" + repo_name = repo_w_no_tags_conventional_commits_unmasked_initial_release.__name__ + commit_type: CommitConvention = ( + repo_name.split("_commits", maxsplit=1)[0].split("_")[-1] # type: ignore[assignment] + ) + + def _build_repo(cached_repo_path: Path) -> Sequence[RepoActions]: + repo_construction_steps = get_repo_definition_4_trunk_only_repo_w_no_tags( + commit_type=commit_type, + extra_configs={ + "tool.semantic_release.changelog.default_templates.mask_initial_release": False, + }, + ) + return build_repo_from_definition(cached_repo_path, repo_construction_steps) + + build_repo_or_copy_cache( + repo_name=repo_name, + build_spec_hash=build_spec_hash_4_repo_w_no_tags, + build_repo_func=_build_repo, + dest_dir=example_project_dir, + ) + + if not (cached_repo_data := get_cached_repo_data(proj_dirname=repo_name)): + raise ValueError("Failed to retrieve repo data from cache") + + return { + "definition": cached_repo_data["build_definition"], + "repo": example_project_git_repo(), + } + + @pytest.fixture def repo_w_no_tags_conventional_commits( build_trunk_only_repo_w_no_tags: BuildSpecificRepoFn, diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py b/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py index ad4515268..a72ef1e20 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py @@ -85,7 +85,7 @@ def _get_repo_from_definition( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py b/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py index 08ed83be3..40741bc18 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py @@ -87,7 +87,7 @@ def _get_repo_from_definition( hvcs_domain: str = EXAMPLE_HVCS_DOMAIN, tag_format_str: str | None = None, extra_configs: dict[str, TomlSerializableTypes] | None = None, - mask_initial_release: bool = False, + mask_initial_release: bool = True, ignore_merge_commits: bool = True, ) -> Sequence[RepoActions]: stable_now_datetime = stable_now_date() From 7d39e7675f859463b54751d59957b869d5d8395c Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Tue, 20 May 2025 19:29:01 -0600 Subject: [PATCH 081/120] refactor(config)!: change `changelog.mode` default to `update` Changes the default behavior of PSR when `changelog.mode` setting is not provided. BREAKING CHANGE: This release switches the `changelog.mode` default to `update`. In this mode, if a changelog exists, PSR will update the changelog **IF AND ONLY IF** the configured insertion flag exists in the changelog. The Changelog output will remain unchanged if no insertion flag exists. The insertion flag may be configured with the `changelog.insertion_flag` setting. When upgrading to `v10`, you must add the insertion flag manually or you can just delete the changelog file and run PSR's changelog generation and it will rebuild the changelog (similar to init mode) but it will add the insertion flag. If your configuration already sets the `changelog.mode` value, then this change will have no effect on your project. If you would rather the changelog be generated from scratch every release, than set the `changelog.mode` value to `init` in your configuration. --- src/semantic_release/cli/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/semantic_release/cli/config.py b/src/semantic_release/cli/config.py index ce40ca0b9..59df69834 100644 --- a/src/semantic_release/cli/config.py +++ b/src/semantic_release/cli/config.py @@ -157,7 +157,7 @@ class ChangelogConfig(BaseModel): ) environment: ChangelogEnvironmentConfig = ChangelogEnvironmentConfig() exclude_commit_patterns: Tuple[str, ...] = () - mode: ChangelogMode = ChangelogMode.INIT + mode: ChangelogMode = ChangelogMode.UPDATE insertion_flag: str = "" template_dir: str = "templates" From 0bed9069df67ae806ad0a15f8434ac4efcc6ba31 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Tue, 20 May 2025 19:43:11 -0600 Subject: [PATCH 082/120] docs(configuration): change the default value for `changelog.mode` in the setting description # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch brk/changelog-update-as-default # Changes to be committed: # modified: docs/configuration.rst # --- docs/configuration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index b4b9b2b23..34411e579 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -664,7 +664,7 @@ The patterns in this list are treated as regular expressions. ``mode`` ******** -*Introduced in v9.10.0* +*Introduced in v9.10.0. Default changed to `update` in $NEW_VERSION.* **Type:** ``Literal["init", "update"]`` @@ -682,7 +682,7 @@ version information at that location. If you are using a custom template directory, the `context.changelog_mode` value will exist in the changelog context but it is up to your implementation to determine if and/or how to use it. -**Default:** ``init`` +**Default:** ``update`` .. seealso:: - :ref:`changelog-templates-default_changelog` From 7d58fde8146fcdefb64631f287be4f5df24b6bee Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Tue, 20 May 2025 23:53:18 -0600 Subject: [PATCH 083/120] test(cmd-version): update tests of repo builders for `changelog.mode=update` --- .../e2e/cmd_version/bump_version/conftest.py | 107 +++++++++++++++--- .../git_flow/test_repo_1_channel.py | 31 ++--- .../git_flow/test_repo_2_channels.py | 31 ++--- .../git_flow/test_repo_3_channels.py | 32 ++---- .../git_flow/test_repo_4_channels.py | 31 ++--- .../github_flow/test_repo_1_channel.py | 31 ++--- .../github_flow/test_repo_2_channels.py | 31 ++--- .../trunk_based_dev/test_repo_trunk.py | 31 ++--- .../test_repo_trunk_dual_version_support.py | 29 ++--- ...runk_dual_version_support_w_prereleases.py | 49 ++------ .../test_repo_trunk_w_prereleases.py | 51 ++------- tests/e2e/conftest.py | 14 +-- 12 files changed, 196 insertions(+), 272 deletions(-) diff --git a/tests/e2e/cmd_version/bump_version/conftest.py b/tests/e2e/cmd_version/bump_version/conftest.py index 23924b579..da36ff1d2 100644 --- a/tests/e2e/cmd_version/bump_version/conftest.py +++ b/tests/e2e/cmd_version/bump_version/conftest.py @@ -1,15 +1,23 @@ from __future__ import annotations -import shutil from typing import TYPE_CHECKING import pytest from git import Repo +from semantic_release.hvcs.github import Github + +from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD +from tests.util import assert_successful_exit_code + if TYPE_CHECKING: from pathlib import Path from typing import Protocol + from click.testing import Result + + from tests.conftest import RunCliFn + from tests.fixtures.example_project import UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuildRepoFromDefinitionFn, RepoActionConfigure class InitMirrorRepo4RebuildFn(Protocol): @@ -19,13 +27,19 @@ def __call__( configuration_step: RepoActionConfigure, ) -> Path: ... + class RunPSReleaseFn(Protocol): + def __call__( + self, + next_version_str: str, + git_repo: Repo, + ) -> Result: ... + @pytest.fixture(scope="session") def init_mirror_repo_for_rebuild( - default_changelog_md_template: Path, - default_changelog_rst_template: Path, - changelog_template_dir: Path, build_repo_from_definition: BuildRepoFromDefinitionFn, + changelog_md_file: Path, + changelog_rst_file: Path, ) -> InitMirrorRepo4RebuildFn: def _init_mirror_repo_for_rebuild( mirror_repo_dir: Path, @@ -40,21 +54,80 @@ def _init_mirror_repo_for_rebuild( repo_construction_steps=[configuration_step], ) - # Force custom changelog to be a copy of the default changelog (md and rst) - shutil.copytree( - src=default_changelog_md_template.parent, - dst=mirror_repo_dir / changelog_template_dir, - dirs_exist_ok=True, - ) - shutil.copytree( - src=default_changelog_rst_template.parent, - dst=mirror_repo_dir / changelog_template_dir, - dirs_exist_ok=True, - ) - with Repo(mirror_repo_dir) as mirror_git_repo: - mirror_git_repo.git.add(str(changelog_template_dir)) + # remove the default changelog files to enable Update Mode (new default of v10) + mirror_git_repo.git.rm(str(changelog_md_file), force=True) + mirror_git_repo.git.rm(str(changelog_rst_file), force=True) return mirror_repo_dir return _init_mirror_repo_for_rebuild + + +@pytest.fixture(scope="session") +def run_psr_release( + run_cli: RunCliFn, + changelog_rst_file: Path, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> RunPSReleaseFn: + base_version_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD] + write_changelog_only_cmd = [ + *base_version_cmd, + "--changelog", + "--no-commit", + "--no-tag", + "--skip-build", + ] + + def _run_psr_release( + next_version_str: str, + git_repo: Repo, + ) -> Result: + version_n_buildmeta = next_version_str.split("+", maxsplit=1) + version_n_prerelease = version_n_buildmeta[0].split("-", maxsplit=1) + + build_metadata_args = ( + ["--build-metadata", version_n_buildmeta[-1]] + if len(version_n_buildmeta) > 1 + else [] + ) + + prerelease_args = ( + [ + "--as-prerelease", + "--prerelease-token", + version_n_prerelease[-1].split(".", maxsplit=1)[0], + ] + if len(version_n_prerelease) > 1 + else [] + ) + + # Initial run to write the RST changelog + # 1. configure PSR to write the RST changelog with the RST default insertion flag + update_pyproject_toml( + "tool.semantic_release.changelog.default_templates.changelog_file", + str(changelog_rst_file), + ) + cli_cmd = [*write_changelog_only_cmd, *prerelease_args, *build_metadata_args] + result = run_cli(cli_cmd[1:], env={Github.DEFAULT_ENV_TOKEN_NAME: "1234"}) + assert_successful_exit_code(result, cli_cmd) + + # Reset the index in case PSR added anything to the index + git_repo.git.reset("--mixed", "HEAD") + + # Add the changelog file to the git index but reset the working directory + git_repo.git.add(str(changelog_rst_file)) + git_repo.git.checkout("--", ".") + + # Actual run to release & write the MD changelog + cli_cmd = [ + *base_version_cmd, + *prerelease_args, + *build_metadata_args, + ] + result = run_cli(cli_cmd[1:], env={Github.DEFAULT_ENV_TOKEN_NAME: "1234"}) + assert_successful_exit_code(result, cli_cmd) + + return result + + return _run_psr_release diff --git a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_1_channel.py b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_1_channel.py index 979a1ad8e..e257448f8 100644 --- a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_1_channel.py +++ b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_1_channel.py @@ -7,16 +7,12 @@ from flatdict import FlatDict from freezegun import freeze_time -from tests.const import ( - MAIN_PROG_NAME, - VERSION_SUBCMD, -) from tests.fixtures.repos.git_flow import ( repo_w_git_flow_conventional_commits, repo_w_git_flow_emoji_commits, repo_w_git_flow_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -24,8 +20,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -53,7 +51,7 @@ ) def test_gitflow_repo_rebuild_1_channel( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_git_flow_repo_w_1_release_channels: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -131,19 +129,10 @@ def test_gitflow_repo_rebuild_1_channel( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message @@ -157,7 +146,7 @@ def test_gitflow_repo_rebuild_1_channel( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_2_channels.py b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_2_channels.py index 5ffc6bef4..2f6b30c76 100644 --- a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_2_channels.py +++ b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_2_channels.py @@ -7,16 +7,12 @@ from flatdict import FlatDict from freezegun import freeze_time -from tests.const import ( - MAIN_PROG_NAME, - VERSION_SUBCMD, -) from tests.fixtures.repos.git_flow import ( repo_w_git_flow_w_alpha_prereleases_n_conventional_commits, repo_w_git_flow_w_alpha_prereleases_n_emoji_commits, repo_w_git_flow_w_alpha_prereleases_n_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -24,8 +20,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -53,7 +51,7 @@ ) def test_gitflow_repo_rebuild_2_channels( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_git_flow_repo_w_2_release_channels: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -131,19 +129,10 @@ def test_gitflow_repo_rebuild_2_channels( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message @@ -157,7 +146,7 @@ def test_gitflow_repo_rebuild_2_channels( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_3_channels.py b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_3_channels.py index c9bd6ccc5..a4dc00675 100644 --- a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_3_channels.py +++ b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_3_channels.py @@ -7,17 +7,13 @@ from flatdict import FlatDict from freezegun import freeze_time -from tests.const import ( - MAIN_PROG_NAME, - VERSION_SUBCMD, -) from tests.fixtures.repos.git_flow import ( repo_w_git_flow_w_rc_n_alpha_prereleases_n_conventional_commits, repo_w_git_flow_w_rc_n_alpha_prereleases_n_conventional_commits_using_tag_format, repo_w_git_flow_w_rc_n_alpha_prereleases_n_emoji_commits, repo_w_git_flow_w_rc_n_alpha_prereleases_n_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -25,8 +21,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -55,7 +53,7 @@ ) def test_gitflow_repo_rebuild_3_channels( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_git_flow_repo_w_3_release_channels: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -133,20 +131,10 @@ def test_gitflow_repo_rebuild_3_channels( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = run_cli(cli_cmd[1:]) - # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message actual_pyproject_toml_content = (mirror_repo_dir / "pyproject.toml").read_text() @@ -159,7 +147,7 @@ def test_gitflow_repo_rebuild_3_channels( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_4_channels.py b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_4_channels.py index e031d86d4..eeeaa7598 100644 --- a/tests/e2e/cmd_version/bump_version/git_flow/test_repo_4_channels.py +++ b/tests/e2e/cmd_version/bump_version/git_flow/test_repo_4_channels.py @@ -7,16 +7,12 @@ from flatdict import FlatDict from freezegun import freeze_time -from tests.const import ( - MAIN_PROG_NAME, - VERSION_SUBCMD, -) from tests.fixtures.repos.git_flow import ( repo_w_git_flow_w_beta_alpha_rev_prereleases_n_conventional_commits, repo_w_git_flow_w_beta_alpha_rev_prereleases_n_emoji_commits, repo_w_git_flow_w_beta_alpha_rev_prereleases_n_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -24,8 +20,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -53,7 +51,7 @@ ) def test_gitflow_repo_rebuild_4_channels( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_git_flow_repo_w_4_release_channels: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -131,19 +129,10 @@ def test_gitflow_repo_rebuild_4_channels( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message @@ -157,7 +146,7 @@ def test_gitflow_repo_rebuild_4_channels( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/cmd_version/bump_version/github_flow/test_repo_1_channel.py b/tests/e2e/cmd_version/bump_version/github_flow/test_repo_1_channel.py index fe166f540..2dec4e393 100644 --- a/tests/e2e/cmd_version/bump_version/github_flow/test_repo_1_channel.py +++ b/tests/e2e/cmd_version/bump_version/github_flow/test_repo_1_channel.py @@ -7,16 +7,12 @@ from flatdict import FlatDict from freezegun import freeze_time -from tests.const import ( - MAIN_PROG_NAME, - VERSION_SUBCMD, -) from tests.fixtures.repos.github_flow import ( repo_w_github_flow_w_default_release_channel_conventional_commits, repo_w_github_flow_w_default_release_channel_emoji_commits, repo_w_github_flow_w_default_release_channel_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -24,8 +20,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -53,7 +51,7 @@ ) def test_githubflow_repo_rebuild_1_channel( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_repo_w_github_flow_w_default_release_channel: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -131,19 +129,10 @@ def test_githubflow_repo_rebuild_1_channel( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message @@ -157,7 +146,7 @@ def test_githubflow_repo_rebuild_1_channel( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/cmd_version/bump_version/github_flow/test_repo_2_channels.py b/tests/e2e/cmd_version/bump_version/github_flow/test_repo_2_channels.py index 3f944fd3b..8d2ebd3c3 100644 --- a/tests/e2e/cmd_version/bump_version/github_flow/test_repo_2_channels.py +++ b/tests/e2e/cmd_version/bump_version/github_flow/test_repo_2_channels.py @@ -7,16 +7,12 @@ from flatdict import FlatDict from freezegun import freeze_time -from tests.const import ( - MAIN_PROG_NAME, - VERSION_SUBCMD, -) from tests.fixtures.repos.github_flow import ( repo_w_github_flow_w_feature_release_channel_conventional_commits, repo_w_github_flow_w_feature_release_channel_emoji_commits, repo_w_github_flow_w_feature_release_channel_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -24,8 +20,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -53,7 +51,7 @@ ) def test_githubflow_repo_rebuild_2_channels( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_repo_w_github_flow_w_feature_release_channel: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -131,19 +129,10 @@ def test_githubflow_repo_rebuild_2_channels( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message @@ -157,7 +146,7 @@ def test_githubflow_repo_rebuild_2_channels( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk.py b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk.py index e091b5d1e..d079b6638 100644 --- a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk.py +++ b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk.py @@ -7,16 +7,12 @@ from flatdict import FlatDict from freezegun import freeze_time -from tests.const import ( - MAIN_PROG_NAME, - VERSION_SUBCMD, -) from tests.fixtures.repos.trunk_based_dev import ( repo_w_trunk_only_conventional_commits, repo_w_trunk_only_emoji_commits, repo_w_trunk_only_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -24,8 +20,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -55,7 +53,7 @@ ) def test_trunk_repo_rebuild_only_official_releases( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_trunk_only_repo_w_tags: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -133,19 +131,10 @@ def test_trunk_repo_rebuild_only_official_releases( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message @@ -159,7 +148,7 @@ def test_trunk_repo_rebuild_only_official_releases( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support.py b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support.py index f2f8c0ccf..f68bf817e 100644 --- a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support.py +++ b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support.py @@ -9,15 +9,13 @@ from tests.const import ( DEFAULT_BRANCH_NAME, - MAIN_PROG_NAME, - VERSION_SUBCMD, ) from tests.fixtures.repos.trunk_based_dev import ( repo_w_trunk_only_dual_version_spt_conventional_commits, repo_w_trunk_only_dual_version_spt_emoji_commits, repo_w_trunk_only_dual_version_spt_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -25,8 +23,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -54,7 +54,7 @@ ) def test_trunk_repo_rebuild_dual_version_spt_official_releases_only( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_trunk_only_repo_w_dual_version_support: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -137,19 +137,10 @@ def test_trunk_repo_rebuild_dual_version_spt_official_releases_only( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [MAIN_PROG_NAME, "--strict", VERSION_SUBCMD, *build_metadata_args] - result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message @@ -163,7 +154,7 @@ def test_trunk_repo_rebuild_dual_version_spt_official_releases_only( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support_w_prereleases.py b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support_w_prereleases.py index bd00935f7..1514dac38 100644 --- a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support_w_prereleases.py +++ b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_dual_version_support_w_prereleases.py @@ -9,15 +9,13 @@ from tests.const import ( DEFAULT_BRANCH_NAME, - MAIN_PROG_NAME, - VERSION_SUBCMD, ) from tests.fixtures.repos.trunk_based_dev import ( repo_w_trunk_only_dual_version_spt_w_prereleases_conventional_commits, repo_w_trunk_only_dual_version_spt_w_prereleases_emoji_commits, repo_w_trunk_only_dual_version_spt_w_prereleases_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -25,8 +23,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -54,7 +54,7 @@ ) def test_trunk_repo_rebuild_dual_version_spt_w_official_n_prereleases( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_trunk_only_repo_w_dual_version_spt_w_prereleases: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -137,39 +137,10 @@ def test_trunk_repo_rebuild_dual_version_spt_w_official_n_prereleases( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] - ) - prerelease_args = ( - [ - "--as-prerelease", - "--prerelease-token", - ( - release_action_step["details"]["version"] - .split("-", maxsplit=1)[-1] - .split(".", maxsplit=1)[0] - ), - ] - if len(release_action_step["details"]["version"].split("-", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [ - MAIN_PROG_NAME, - "--strict", - VERSION_SUBCMD, - *build_metadata_args, - *prerelease_args, - ] - result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message @@ -183,7 +154,7 @@ def test_trunk_repo_rebuild_dual_version_spt_w_official_n_prereleases( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_w_prereleases.py b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_w_prereleases.py index 0d0aff235..67af5d56a 100644 --- a/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_w_prereleases.py +++ b/tests/e2e/cmd_version/bump_version/trunk_based_dev/test_repo_trunk_w_prereleases.py @@ -7,16 +7,12 @@ from flatdict import FlatDict from freezegun import freeze_time -from tests.const import ( - MAIN_PROG_NAME, - VERSION_SUBCMD, -) from tests.fixtures.repos.trunk_based_dev import ( repo_w_trunk_only_n_prereleases_conventional_commits, repo_w_trunk_only_n_prereleases_emoji_commits, repo_w_trunk_only_n_prereleases_scipy_commits, ) -from tests.util import assert_successful_exit_code, temporary_working_directory +from tests.util import temporary_working_directory if TYPE_CHECKING: from pathlib import Path @@ -24,8 +20,10 @@ from requests_mock import Mocker - from tests.conftest import RunCliFn - from tests.e2e.cmd_version.bump_version.conftest import InitMirrorRepo4RebuildFn + from tests.e2e.cmd_version.bump_version.conftest import ( + InitMirrorRepo4RebuildFn, + RunPSReleaseFn, + ) from tests.e2e.conftest import GetSanitizedChangelogContentFn from tests.fixtures.example_project import ExProjectDir from tests.fixtures.git_repo import ( @@ -53,7 +51,7 @@ ) def test_trunk_repo_rebuild_w_prereleases( repo_fixture_name: str, - run_cli: RunCliFn, + run_psr_release: RunPSReleaseFn, build_trunk_only_repo_w_prerelease_tags: BuildSpecificRepoFn, split_repo_actions_by_release_tags: SplitRepoActionsByReleaseTagsFn, init_mirror_repo_for_rebuild: InitMirrorRepo4RebuildFn, @@ -131,39 +129,10 @@ def test_trunk_repo_rebuild_w_prereleases( with freeze_time( release_action_step["details"]["datetime"] ), temporary_working_directory(mirror_repo_dir): - build_metadata_args = ( - [ - "--build-metadata", - release_action_step["details"]["version"].split("+", maxsplit=1)[ - -1 - ], - ] - if len(release_action_step["details"]["version"].split("+", maxsplit=1)) - > 1 - else [] - ) - prerelease_args = ( - [ - "--as-prerelease", - "--prerelease-token", - ( - release_action_step["details"]["version"] - .split("-", maxsplit=1)[-1] - .split(".", maxsplit=1)[0] - ), - ] - if len(release_action_step["details"]["version"].split("-", maxsplit=1)) - > 1 - else [] + run_psr_release( + next_version_str=release_action_step["details"]["version"], + git_repo=mirror_git_repo, ) - cli_cmd = [ - MAIN_PROG_NAME, - "--strict", - VERSION_SUBCMD, - *build_metadata_args, - *prerelease_args, - ] - result = run_cli(cli_cmd[1:]) # take measurement after running the version command actual_release_commit_text = mirror_git_repo.head.commit.message @@ -177,7 +146,7 @@ def test_trunk_repo_rebuild_w_prereleases( ) # Evaluate (normal release actions should have occurred as expected) - assert_successful_exit_code(result, cli_cmd) + # ------------------------------------------------------------------ # Make sure version file is updated assert expected_pyproject_toml_content == actual_pyproject_toml_content assert expected_version_file_content == actual_version_file_content diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 66aa8ab3d..b64d5aecf 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -137,11 +137,10 @@ def get_sanitized_rst_changelog_content( def _get_sanitized_rst_changelog_content( repo_dir: Path, - remove_insertion_flag: bool = True, + remove_insertion_flag: bool = False, ) -> str: - # TODO: v10 change -- default turns to update so this is not needed - # Because we are in init mode, the insertion flag is not present in the changelog - # we must take it out manually because our repo generation fixture includes it automatically + # Note that our repo generation fixture includes the insertion flag automatically + # toggle remove_insertion_flag to True to remove the insertion flag, applies to Init mode repos with (repo_dir / changelog_rst_file).open(newline=os.linesep) as rfd: # use os.linesep here because the insertion flag is os-specific # but convert the content to universal newlines for comparison @@ -169,11 +168,10 @@ def get_sanitized_md_changelog_content( ) -> GetSanitizedChangelogContentFn: def _get_sanitized_md_changelog_content( repo_dir: Path, - remove_insertion_flag: bool = True, + remove_insertion_flag: bool = False, ) -> str: - # TODO: v10 change -- default turns to update so this is not needed - # Because we are in init mode, the insertion flag is not present in the changelog - # we must take it out manually because our repo generation fixture includes it automatically + # Note that our repo generation fixture includes the insertion flag automatically + # toggle remove_insertion_flag to True to remove the insertion flag, applies to Init mode repos with (repo_dir / changelog_md_file).open(newline=os.linesep) as rfd: # use os.linesep here because the insertion flag is os-specific # but convert the content to universal newlines for comparison From 3fbde64a42f480607fc45236e55f72680e2c03e2 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 24 May 2025 22:29:16 -0600 Subject: [PATCH 084/120] chore(config): update semantic-release default branch regular expression --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7c1ae30c9..addaf110e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -436,7 +436,7 @@ mode = "update" template_dir = "config/release-templates" [tool.semantic_release.branches.main] -match = "(main|master)" +match = "^(main|master)$" prerelease = false prerelease_token = "rc" From a5f5e042ae9af909ee9e3ddf57c78adbc92ce378 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 24 May 2025 15:09:54 -0600 Subject: [PATCH 085/120] docs(upgrading): re-locate version upgrade guides into `Upgrading PSR` --- CHANGELOG.rst | 2 +- docs/index.rst | 2 +- .../08-upgrade.rst} | 74 +++++++++---------- docs/upgrading/09-upgrade.rst | 11 +++ docs/upgrading/index.rst | 26 +++++++ 5 files changed, 76 insertions(+), 39 deletions(-) rename docs/{migrating_from_v7.rst => upgrading/08-upgrade.rst} (92%) create mode 100644 docs/upgrading/09-upgrade.rst create mode 100644 docs/upgrading/index.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e802155f2..5714ef3eb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2310,7 +2310,7 @@ v8.0.0 (2023-07-16) πŸ’₯ BREAKING CHANGES -------------------- -* numerous breaking changes, see :ref:`migrating-from-v7` for more information +* numerous breaking changes, see :ref:`upgrade_v8` for more information .. _ec30564: https://github.com/python-semantic-release/python-semantic-release/commit/ec30564b4ec732c001d76d3c09ba033066d2b6fe .. _PR#619: https://github.com/python-semantic-release/python-semantic-release/pull/619 diff --git a/docs/index.rst b/docs/index.rst index f8273c5b7..b5ac6204c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -66,7 +66,7 @@ Documentation Contents troubleshooting contributing contributors - Migrating from Python Semantic Release v7 + upgrading/index Internal API Algorithm Changelog diff --git a/docs/migrating_from_v7.rst b/docs/upgrading/08-upgrade.rst similarity index 92% rename from docs/migrating_from_v7.rst rename to docs/upgrading/08-upgrade.rst index be4cbc14a..a6ce7a652 100644 --- a/docs/migrating_from_v7.rst +++ b/docs/upgrading/08-upgrade.rst @@ -1,9 +1,9 @@ -.. _migrating-from-v7: +.. _upgrade_v8: -Migrating from Python Semantic Release v7 -========================================= +Upgrading to v8 +=============== -Python Semantic Release 8.0.0 introduced a number of breaking changes. +Python Semantic Release v8.0.0 introduced a number of breaking changes. The internals have been changed significantly to better support highly-requested features and to streamline the maintenance of the project. @@ -12,18 +12,18 @@ exhibit different behavior to earlier versions of Python Semantic Release. This page is a guide to help projects to ``pip install python-semantic-release>=8.0.0`` with fewer surprises. -.. _breaking-github-action: +.. _upgrade_v8-github-action: Python Semantic Release GitHub Action ------------------------------------- -.. _breaking-removed-artefact-upload: +.. _upgrade_v8-removed-artefact-upload: GitHub Action no longer publishes artifacts to PyPI or GitHub Releases """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Python Semantic Release no longer uploads distributions to PyPI - see -:ref:`breaking-commands-repurposed-version-and-publish`. If you are +:ref:`upgrade_v8-commands-repurposed-version-and-publish`. If you are using Python Semantic Release to publish release notes and artifacts to GitHub releases, there is a new GitHub Action `upload-to-gh-release`_ which will perform this action for you. @@ -111,7 +111,7 @@ GitHub Action: .. _upload-to-gh-release: https://github.com/python-semantic-release/upload-to-gh-release .. _pypa/gh-action-pypi-publish: https://github.com/pypa/gh-action-pypi-publish -.. _breaking-github-action-removed-pypi-token: +.. _upgrade_v8-github-action-removed-pypi-token: Removal of ``pypi_token``, ``repository_username`` and ``repository_password`` inputs """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -121,7 +121,7 @@ Since the library no longer supports publishing to PyPI, the ``pypi_token``, all been removed. See the above section for how to publish to PyPI using the official GitHub Action from the Python Packaging Authority (PyPA). -.. _breaking-options-inputs: +.. _upgrade_v8-options-inputs: Rename ``additional_options`` to ``root_options`` """"""""""""""""""""""""""""""""""""""""""""""""" @@ -132,12 +132,12 @@ reason, and because the usage of the CLI has changed, ``additional_options`` has been renamed to ``root_options`` to reflect the fact that the options are for the main :ref:`cmd-main` command group. -.. _breaking-commands: +.. _upgrade_v8-commands: Commands -------- -.. _breaking-commands-repurposed-version-and-publish: +.. _upgrade_v8-commands-repurposed-version-and-publish: Repurposing of ``version`` and ``publish`` commands """"""""""""""""""""""""""""""""""""""""""""""""""" @@ -189,7 +189,7 @@ With steps 1-6 being handled by the :ref:`cmd-version` command, step 7 being lef to the developer to handle, and lastly step 8 to be handled by the :ref:`cmd-publish` command. -.. _breaking-removed-define-option: +.. _upgrade_v8-removed-define-option: Removal of ``-D/--define`` command-line option """""""""""""""""""""""""""""""""""""""""""""" @@ -206,7 +206,7 @@ specify using just command-line options. .. _#600: https://github.com/python-semantic-release/python-semantic-release/issues/600 -.. _breaking-commands-no-verify-ci: +.. _upgrade_v8-commands-no-verify-ci: Removal of CI verifications """"""""""""""""""""""""""" @@ -230,7 +230,7 @@ shell commands *before* invoking ``semantic-release`` to verify your environment (e.g. via ``export RELEASE_BRANCH=main`` and/or replace the variable with the branch name you want to verify the CI environment for. -.. _breaking-commands-no-verify-ci-travis: +.. _upgrade_v8-commands-no-verify-ci-travis: Travis ~~~~~~ @@ -249,7 +249,7 @@ Travis fi -.. _breaking-commands-no-verify-ci-semaphore: +.. _upgrade_v8-commands-no-verify-ci-semaphore: Semaphore ~~~~~~~~~ @@ -269,7 +269,7 @@ Semaphore fi -.. _breaking-commands-no-verify-ci-frigg: +.. _upgrade_v8-commands-no-verify-ci-frigg: Frigg ~~~~~ @@ -287,7 +287,7 @@ Frigg exit 1 fi -.. _breaking-commands-no-verify-ci-circle-ci: +.. _upgrade_v8-commands-no-verify-ci-circle-ci: Circle CI ~~~~~~~~~ @@ -305,7 +305,7 @@ Circle CI exit 1 fi -.. _breaking-commands-no-verify-ci-gitlab-ci: +.. _upgrade_v8-commands-no-verify-ci-gitlab-ci: GitLab CI ~~~~~~~~~ @@ -320,7 +320,7 @@ GitLab CI exit 1 fi -.. _breaking-commands-no-verify-ci-bitbucket: +.. _upgrade_v8-commands-no-verify-ci-bitbucket: **Condition**: environment variable ``BITBUCKET_BUILD_NUMBER`` is set @@ -335,7 +335,7 @@ GitLab CI exit 1 fi -.. _breaking-commands-no-verify-ci-jenkins: +.. _upgrade_v8-commands-no-verify-ci-jenkins: Jenkins ~~~~~~~ @@ -359,7 +359,7 @@ Jenkins exit 1 fi -.. _breaking-removed-build-status-checking: +.. _upgrade_v8-removed-build-status-checking: Removal of Build Status Checking """""""""""""""""""""""""""""""" @@ -368,7 +368,7 @@ Prior to v8, Python Semantic Release contained a configuration option, ``check_build_status``, which would attempt to prevent a release being made if it was possible to identify that a corresponding build pipeline was failing. For similar reasons to those motivating the removal of -:ref:`CI Checks `, this feature has also been removed. +:ref:`CI Checks `, this feature has also been removed. If you are leveraging this feature in Python Semantic Release v7, the following bash commands will replace the functionality, and you can add these to your pipeline. @@ -386,7 +386,7 @@ installed, you can download it from `the curl website`_ .. _installation guide for jq: https://jqlang.github.io/jq/download/ .. _the curl website: https://curl.se/ -.. _breaking-removed-build-status-checking-github: +.. _upgrade_v8-removed-build-status-checking-github: GitHub ~~~~~~ @@ -407,7 +407,7 @@ GitHub Note that ``$GITHUB_API_DOMAIN`` is typically ``api.github.com`` unless you are using GitHub Enterprise with a custom domain name. -.. _breaking-removed-build-status-checking-gitea: +.. _upgrade_v8-removed-build-status-checking-gitea: Gitea ~~~~~ @@ -425,7 +425,7 @@ Gitea exit 1 fi -.. _breaking-removed-build-status-checking-gitlab: +.. _upgrade_v8-removed-build-status-checking-gitlab: Gitlab ~~~~~~ @@ -451,7 +451,7 @@ Gitlab done -.. _breaking-commands-multibranch-releases: +.. _upgrade_v8-commands-multibranch-releases: Multibranch releases """""""""""""""""""" @@ -462,7 +462,7 @@ has been changed - you must manually check out the branch which you would like t against, and if you would like to create releases against this branch you must also ensure that it belongs to a :ref:`release group `. -.. _breaking-commands-changelog: +.. _upgrade_v8-commands-changelog: ``changelog`` command """"""""""""""""""""" @@ -477,7 +477,7 @@ tag ``v1.1.4``, you should run:: semantic-release changelog --post-to-release-tag v1.1.4 -.. _breaking-changelog-customization: +.. _upgrade_v8-changelog-customization: Changelog customization """"""""""""""""""""""" @@ -492,7 +492,7 @@ fully open up customizing the changelog's appearance. .. _Jinja: https://jinja.palletsprojects.com/en/3.1.x/ -.. _breaking-configuration: +.. _upgrade_v8-configuration: Configuration ------------- @@ -501,7 +501,7 @@ The configuration structure has been completely reworked, so you should read :ref:`configuration` carefully during the process of upgrading to v8+. However, some common pitfalls and potential sources of confusion are summarized here. -.. _breaking-configuration-setup-cfg-unsupported: +.. _upgrade_v8-configuration-setup-cfg-unsupported: ``setup.cfg`` is no longer supported """""""""""""""""""""""""""""""""""" @@ -532,7 +532,7 @@ needs. .. _pip issue: https://github.com/pypa/pip/issues/8437#issuecomment-805313362 -.. _breaking-commit-parser-options: +.. _upgrade_v8-commit-parser-options: Commit parser options """"""""""""""""""""" @@ -547,7 +547,7 @@ and if you need to parse multiple commit styles for a single project it's recomm that you create a parser following :ref:`commit_parser-custom_parser` that is tailored to the specific needs of your project. -.. _breaking-version-variable-rename: +.. _upgrade_v8-version-variable-rename: ``version_variable`` """""""""""""""""""" @@ -555,7 +555,7 @@ is tailored to the specific needs of your project. This option has been renamed to :ref:`version_variables ` as it refers to a list of variables which can be updated. -.. _breaking-version-pattern-removed: +.. _upgrade_v8-version-pattern-removed: ``version_pattern`` """"""""""""""""""" @@ -567,7 +567,7 @@ for a project and store this in an environment variable like so:: export VERSION=$(semantic-release version --print) -.. _breaking-version-toml-type: +.. _upgrade_v8-version-toml-type: ``version_toml`` """""""""""""""" @@ -588,7 +588,7 @@ simply wrap the value in ``[]``: version_toml = ["pyproject.toml:tool.poetry.version"] -.. _breaking-tag-format-validation: +.. _upgrade_v8-tag-format-validation: ``tag_format`` """""""""""""" @@ -597,7 +597,7 @@ This option has the same effect as it did in Python Semantic Release prior to v8 but Python Semantic Release will now verify that it has a ``{version}`` format key and raise an error if this is not the case. -.. _breaking-upload-to-release-rename: +.. _upgrade_v8-upload-to-release-rename: ``upload_to_release`` """"""""""""""""""""" @@ -605,7 +605,7 @@ key and raise an error if this is not the case. This option has been renamed to :ref:`upload_to_vcs_release `. -.. _breaking-custom-commit-parsers: +.. _upgrade_v8-custom-commit-parsers: Custom Commit Parsers --------------------- diff --git a/docs/upgrading/09-upgrade.rst b/docs/upgrading/09-upgrade.rst new file mode 100644 index 000000000..10c0d76e9 --- /dev/null +++ b/docs/upgrading/09-upgrade.rst @@ -0,0 +1,11 @@ +.. _upgrade_v9: + +Upgrading to v9 +=============== + +You are in luck! The upgrade to ``v9`` is a simple one. + +The breaking change for this version is the removal of support for **Python 3.7**, as it has passed +End-Of-Life (EOL). This means that if you are using Python 3.7, you will need to upgrade +to at least Python 3.8 in order to use ``v9``. This will be permanent as all future versions of +``python-semantic-release`` will require Python 3.8 or later. diff --git a/docs/upgrading/index.rst b/docs/upgrading/index.rst new file mode 100644 index 000000000..6b7bcbdd6 --- /dev/null +++ b/docs/upgrading/index.rst @@ -0,0 +1,26 @@ +.. _upgrading: + +============= +Upgrading PSR +============= + +Upgrading PSR is a process that may involve several steps, depending on the version you +are upgrading from and to. This section provides a guide for upgrading from older +versions of PSR to the latest version. + +.. important:: + If you are upgrading across **more than one** major version, you should incrementally + upgrade through each major version and its configuration update guide to ensure a + smooth transition. + + For example, if you are upgrading from v7 to v10, you should first + upgrade to v8 and then to v9, and then lastly to v10 while following the upgrade + guide for each version. At each step you should confirm execution works as expected + before proceeding to the next version. + +.. toctree:: + :caption: Upgrade Guides + :maxdepth: 1 + + Upgrading to v9 <09-upgrade> + Upgrading to v8 <08-upgrade> From 6cd0fbeb44e16d394c210216c7099afa51f5a4a3 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 24 May 2025 15:12:09 -0600 Subject: [PATCH 086/120] docs(algorithm): remove out-of-date algorithm description --- docs/algorithm.rst | 205 --------------------------------------------- docs/index.rst | 1 - 2 files changed, 206 deletions(-) delete mode 100644 docs/algorithm.rst diff --git a/docs/algorithm.rst b/docs/algorithm.rst deleted file mode 100644 index 4ea9fa6dd..000000000 --- a/docs/algorithm.rst +++ /dev/null @@ -1,205 +0,0 @@ -.. _algorithm: - -Python Semantic Release's Version Bumping Algorithm -=================================================== - -Below is a technical description of the algorithm which Python Semantic Release -uses to calculate a new version for a project. - -.. _algorithm-assumptions: - -Assumptions -~~~~~~~~~~~ - -* At runtime, we are in a Git repository with HEAD referring to a commit on - some branch of the repository (i.e. not in detached HEAD state). -* We know in advance whether we want to produce a prerelease or not (based on - the configuration and command-line flags). -* We can parse the tags of the repository into semantic versions, as we are given - the format that those Git tags should follow via configuration, but cannot - cherry-pick only tags that apply to commits on specific branches. We must parse - all tags in order to ensure we have parsed any that might apply to commits in - this branch's history. -* If we can identify a commit as a ``merge-base`` between our HEAD commit and one - or more tags, then that merge-base should be unique. -* We know ahead of time what ``prerelease_token`` to use for prereleases - e.g. - ``rc``. -* We know ahead of time whether ``major`` changes introduced by commits - should cause the new version to remain on ``0.y.z`` if the project is already - on a ``0.`` version - see :ref:`major_on_zero `. - -.. _algorithm-implementation: - -Implementation -~~~~~~~~~~~~~~ - -1. Parse all the Git tags of the repository into semantic versions, and **sort** - in descending (most recent first) order according to `semver precedence`_. - Ignore any tags which do not correspond to valid semantic versions according - to ``tag_format``. - - -2. Find the ``merge-base`` of HEAD and the latest tag according to the sort above. - Call this commit ``M``. - If there are no tags in the repo's history, we set ``M=HEAD``. - -3. Find the latest non-prerelease version whose tag references a commit that is - an ancestor of ``M``. We do this via a breadth-first search through the commit - lineage, starting against ``M``, and for each tag checking if the tag - corresponds to that commit. We break from the search when we find such a tag. - If no such tag is found, see 4a). - Else, suppose that tag corresponds to a commit ``L`` - goto 4b). - -4. - a. If no commit corresponding to the last non-prerelease version is found, - the entire history of the repository is considered. We parse every commit - that is an ancestor of HEAD to determine the type of change introduced - - either ``major``, ``minor``, ``patch``, ``prerelease_revision`` or - ``no_release``. We store this levels in a ``set`` as we only require - the distinct types of change that were introduced. - b. However, if we found a commit ``L`` which is the commit against which the - last non-prerelease was tagged, then we parse only the commits from HEAD - as far back as ``L``, to understand what changes have been introduced - since the previous non-prerelease. We store these levels - either - ``major``, ``minor``, ``patch``, ``prerelease_revision``, or - ``no_release``, in a set, as we only require the distinct types of change - that were introduced. - - c. We look for tags that correspond to each commit during this process, to - identify the latest pre-release that was made within HEAD's ancestry. - -5. If there have been no changes since the last non-prerelease, or all commits - since that release result in a ``no_release`` type according to the commit - parser, then we **terminate the algorithm.** - -6. If we have not exited by this point, we know the following information: - - * The latest version, by `semver precedence`_, within the whole repository. - Call this ``LV``. This might not be within the ancestry of HEAD. - * The latest version, prerelease or non-prerelease, within the whole repository. - Call this ``LVH``. This might not be within the ancestry of HEAD. - This may be the same as ``LV``. - * The latest non-prerelease version within the ancestry of HEAD. Call this - ``LVHF``. This may be the same as ``LVH``. - * The most significant type of change introduced by the commits since the - previous full release. Call this ``level`` - * Whether or not we wish to produce a prerelease from this version increment. - Call this a boolean flag, ``prerelease``. (Assumption) - * Whether or not to increment the major digit if a major change is introduced - against an existing ``0.`` version. Call this ``major_on_zero``, a boolean - flag. (Assumption) - - Using this information, the new version is decided according to the following - criteria: - - a. If ``LV`` has a major digit of ``0``, ``major_on_zero`` is ``False`` and - ``level`` is ``major``, reduce ``level`` to ``minor``. - - b. If ``prerelease=True``, then - - i. Diff ``LV`` with ``LVHF``, to understand if the ``major``, ``minor`` or - ``patch`` digits have changed. For example, diffing ``1.2.1`` and - ``1.2.0`` is a ``patch`` diff, while diffing ``2.1.1`` and ``1.17.2`` is - a ``major`` diff. Call this ``DIFF`` - - ii. If ``DIFF`` is less semantically significant than ``level``, for example - if ``DIFF=patch`` and ``level=minor``, then - - 1. Increment the digit of ``LVF`` corresponding to ``level``, for example - the minor digit if ``level=minor``, setting all less significant - digits to zero. - - 2. Add ``prerelease_token`` as a suffix result of 1., together with a - prerelease revision number of ``1``. Return this new version and - **terminate the algorithm.** - - Thus if ``DIFF=patch``, ``level=minor``, ``prerelease=True``, - ``prerelease_token="rc"``, and ``LVF=1.1.1``, - then the version returned by the algorithm is ``1.2.0-rc.1``. - - iii. If ``DIFF`` is semantically less significant than or equally - significant to ``level``, then this means that the significance - of change introduced by ``level`` is already reflected in a - prerelease version that has been created since the last full release. - For example, if ``LVHF=1.1.1``, ``LV=1.2.0-rc.1`` and ``level=minor``. - - In this case we: - - 1. If the prerelease token of ``LV`` is different from - ``prerelease_token``, take the major, minor and patch digits - of ``LV`` and construct a prerelease version using our given - ``prerelease_token`` and a prerelease revision of ``1``. We - then return this version and **terminate the algorithm.** - - For example, if ``LV=1.2.0-rc.1`` and ``prerelease_token=alpha``, - we return ``1.2.0-alpha.1``. - - 2. If the prerelease token of ``LV`` is the same as ``prerelease_token``, - we increment the revision number of ``LV``, return this version, and - - **terminate the algorithm.** - For example, if ``LV=1.2.0-rc.1`` and ``prerelease_token=rc``, - we return ``1.2.0-rc.2``. - - c. If ``prerelease=False``, then - - i. If ``LV`` is not a prerelease, then we increment the digit of ``LV`` - corresponding to ``level``, for example the minor digit if ``level=minor``, - setting all less significant digits to zero. - We return the result of this and **terminate the algorithm**. - - ii. If ``LV`` is a prerelease, then: - - 1. Diff ``LV`` with ``LVHF``, to understand if the ``major``, ``minor`` or - ``patch`` digits have changed. Call this ``DIFF`` - - 2. If ``DIFF`` is less semantically significant than ``level``, then - - i. Increment the digit of ``LV`` corresponding to ``level``, for example - the minor digit if ``level=minor``, setting all less significant - digits to zero. - - ii. Remove the prerelease token and revision number from the result of i., - ("Finalize" the result of i.) return the result and **terminate the - algorithm.** - - For example, if ``LV=1.2.2-alpha.1`` and ``level=minor``, we return - ``1.3.0``. - - 3. If ``DIFF`` is semantically less significant than or equally - significant to ``level``, then we finalize ``LV``, return the - result and **terminate the algorithm**. - -.. _semver precedence: https://semver.org/#spec-item-11 - -.. _algorithm-complexity: - -Complexity -~~~~~~~~~~ - -**Space:** - -A list of parsed tags takes ``O(number of tags)`` in space. Parsing each commit during -the breadth-first search between ``merge-base`` and the latest tag in the ancestry -of HEAD takes at worst ``O(number of commits)`` in space to track visited commits. -Therefore worst-case space complexity will be linear in the number of commits in the -repo, unless the number of tags significantly exceeds the number of commits -(in which case it will be linear in the number of tags). - -**Time:** - -Assuming using regular expression parsing of each tag is a constant-time operation, -then the following steps contribute to the time complexity of the algorithm: - -* Parsing each tag - ``O(number of tags)`` -* Sorting tags by `semver precedence`_ - - ``O(number of tags * log(number of tags))`` -* Finding the merge-base of HEAD and the latest release tag - - ``O(number of commits)`` (worst case) -* Parsing each commit and checking each tag against each commit - - ``O(number of commits) + O(number of tags * number of commits)`` - (worst case) - -Overall, assuming that the number of tags is less than or equal to the number -of commits in the repository, this would lead to a worst-case time complexity -that's quadratic in the number of commits in the repo. diff --git a/docs/index.rst b/docs/index.rst index b5ac6204c..f0c76e516 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -68,7 +68,6 @@ Documentation Contents contributors upgrading/index Internal API - Algorithm Changelog View on GitHub From 6aad7f17e64fb4717ddd7a9e94d2a730be6a3bd9 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 24 May 2025 15:14:10 -0600 Subject: [PATCH 087/120] docs(github-actions): add reference to manual release workflow example --- docs/automatic-releases/github-actions.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index 621a13067..94aafaf62 100644 --- a/docs/automatic-releases/github-actions.rst +++ b/docs/automatic-releases/github-actions.rst @@ -986,6 +986,15 @@ The equivalent GitHub Action configuration would be: changelog: false build_metadata: abc123 +.. seealso:: + + - `Publish Action Manual Release Workflow`_: To maintain the Publish Action at the same + version as Python Semantic Release, we use a Manual release workflow which forces the + matching bump type as the root project. Check out this workflow to see how you can + manually provide input that triggers the desired version bump. + +.. _Publish Action Manual Release Workflow: https://github.com/python-semantic-release/publish-action/blob/main/.github/workflows/release.yml + .. _gh_actions-monorepo: Actions with Monorepos From 8bed5bcca4a5759af0e3fb24eadf14aa4e4f53c9 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 24 May 2025 15:40:10 -0600 Subject: [PATCH 088/120] docs(contributing): refactor contributing & contributors layout --- AUTHORS.rst | 7 ------- CONTRIBUTING.rst | 4 +++- docs/commands.rst | 7 ++++--- docs/contributing.rst | 1 - docs/contributing/contributing.rst | 1 + docs/contributing/index.rst | 28 ++++++++++++++++++++++++++++ docs/contributors.rst | 1 - docs/index.rst | 3 +-- 8 files changed, 37 insertions(+), 15 deletions(-) delete mode 100644 AUTHORS.rst delete mode 100644 docs/contributing.rst create mode 100644 docs/contributing/contributing.rst create mode 100644 docs/contributing/index.rst delete mode 100644 docs/contributors.rst diff --git a/AUTHORS.rst b/AUTHORS.rst deleted file mode 100644 index 0a7309b92..000000000 --- a/AUTHORS.rst +++ /dev/null @@ -1,7 +0,0 @@ -Contributors ------------- - -|contributors| - -.. |contributors| image:: https://contributors-img.web.app/image?repo=relekang/python-semantic-release - :target: https://github.com/relekang/python-semantic-release/graphs/contributors diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e546295cf..bb728da1d 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,3 +1,5 @@ +.. _contributing_guide: + Contributing ------------ @@ -7,7 +9,7 @@ Please remember to write tests for the cool things you create or fix. Unsure about something? No worries, `open an issue`_. -.. _open an issue: https://github.com/relekang/python-semantic-release/issues/new +.. _open an issue: https://github.com/python-semantic-release/python-semantic-release/issues/new Commit messages ~~~~~~~~~~~~~~~ diff --git a/docs/commands.rst b/docs/commands.rst index e7e8e5f21..30234e9e7 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -23,10 +23,11 @@ Correct:: semantic-release -vv --noop version --print With the exception of :ref:`cmd-main` and :ref:`cmd-generate-config`, all -commands require that you have set up your project's configuration. To help with -this step, :ref:`cmd-generate-config` can create the default configuration for you, -which will allow you to tweak it to your needs rather than write it from scratch. +commands require that you have set up your project's configuration. +To help with setting up your project configuration, :ref:`cmd-generate-config` +will print out the default configuration to the console, which +you can then modify it to match your project & environment. .. _cmd-main: diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index e582053ea..000000000 --- a/docs/contributing.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../CONTRIBUTING.rst diff --git a/docs/contributing/contributing.rst b/docs/contributing/contributing.rst new file mode 100644 index 000000000..ac7b6bcf3 --- /dev/null +++ b/docs/contributing/contributing.rst @@ -0,0 +1 @@ +.. include:: ../../CONTRIBUTING.rst diff --git a/docs/contributing/index.rst b/docs/contributing/index.rst new file mode 100644 index 000000000..51d8945c2 --- /dev/null +++ b/docs/contributing/index.rst @@ -0,0 +1,28 @@ +.. _contributing: + +Contributing +============ + +Love Python Semantic Release? Want to help out? There are many ways you can contribute to the project! + +You can help by: + +- Reporting bugs and issues +- Suggesting new features +- Improving the documentation +- Reviewing pull requests +- Contributing code +- Helping with translations +- Spreading the word about Python Semantic Release +- Participating in discussions +- Testing new features and providing feedback + +No matter how you choose to contribute, please check out our +:ref:`Contributing Guidelines ` and know we appreciate your help! + +**Check out all the folks whom already contributed to Python Semantic Release and become one of them today!** + +|contributors| + +.. |contributors| image:: https://contributors-img.web.app/image?repo=python-semantic-release/python-semantic-release + :target: https://github.com/python-semantic-release/python-semantic-release/graphs/contributors diff --git a/docs/contributors.rst b/docs/contributors.rst deleted file mode 100644 index e122f914a..000000000 --- a/docs/contributors.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../AUTHORS.rst diff --git a/docs/index.rst b/docs/index.rst index f0c76e516..515885a39 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -64,11 +64,10 @@ Documentation Contents Multibranch Releases automatic-releases/index troubleshooting - contributing - contributors upgrading/index Internal API Changelog + Contributing View on GitHub Getting Started From 4ea92ec34dcd45d8cbab24e38e55289617b2d728 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 24 May 2025 15:45:17 -0600 Subject: [PATCH 089/120] docs(upgrading-v10): added migration guide for v9 to v10 --- docs/upgrading/10-upgrade.rst | 189 ++++++++++++++++++++++++++++++++++ docs/upgrading/index.rst | 1 + 2 files changed, 190 insertions(+) create mode 100644 docs/upgrading/10-upgrade.rst diff --git a/docs/upgrading/10-upgrade.rst b/docs/upgrading/10-upgrade.rst new file mode 100644 index 000000000..ffd6b0276 --- /dev/null +++ b/docs/upgrading/10-upgrade.rst @@ -0,0 +1,189 @@ +.. _upgrade_v10: + +Upgrading to v10 +================ + +The upgrade to v10 is primarily motivated by a command injection security vulnerability +found in the GitHub Actions configuration interpreter (see details +:ref:`below `). We also bundled a number of other changes, +including new default configuration values and most importantly, a return to 1-line +commit subjects in the default changelog format. + +For more specific change details for v10, please refer to the :ref:`changelog-v10.0.0` +section of the :ref:`changelog`. + + +.. _upgrade_v10-root_options: + +Security Fix: Command Injection Vulnerability (GitHub Actions) +-------------------------------------------------------------- + +In the previous versions of the GitHub Actions configuration, we used a single +``root_options`` parameter to pass any options you wanted to pass to the +``semantic-release`` main command. This parameter was interpreted as a string and +passed directly to the command line, which made it vulnerable to command injection +attacks. An attacker could exploit this by crafting a malicious string as the +:ref:`gh_actions-psr-inputs-root_options` input, and then it would be executed +as part of the command line, potentially allowing them to run arbitrary commands within +the GitHub Actions Docker container. The ability to exploit this vulnerability is limited +to people whom can modify the GitHub Actions workflow file, which is typically only the +repository maintainers unless you are pointing at an organizational workflow file or +another third-party workflow file. + +To mitigate this vulnerability, we have removed the ``root_options`` parameter completely +and replaced it with individual boolean flag inputs which are then used to select the proper +cli parameters for the ``semantic-release`` command. Additionally, users can protect themselves +by limiting the access to secrets in their GitHub Actions workflows and the permissions of +the GitHub Actions CI TOKEN. + +This vulnerability existed in both the +:ref:`python-semantic-release/python-semantic-release ` and +:ref:`python-semantic-release/publish-action ` actions. + +For the main :ref:`python-semantic-release/python-semantic-release ` action, +the following inputs are now available (in place of the old ``root_options`` parameter): + +- :ref:`gh_actions-psr-inputs-config_file` +- :ref:`gh_actions-psr-inputs-noop` +- :ref:`gh_actions-psr-inputs-strict` +- :ref:`gh_actions-psr-inputs-verbosity` + +For the :ref:`python-semantic-release/publish-action ` action, +the following inputs are now available (in place of the old ``root_options`` parameter): + +- :ref:`gh_actions-publish-inputs-config_file` +- :ref:`gh_actions-publish-inputs-noop` +- :ref:`gh_actions-publish-inputs-verbosity` + + +.. _upgrade_v10-changelog_format-1_line_commit_subjects: + +Changelog Format: 1-Line Commit Subjects +---------------------------------------- + +In v10, the default changelog format has been changed to use 1-line commit subjects instead of +including the full commit message. This change was made to improve the readability of the changelog +as many commit messages are long and contain unnecessary details for the changelog. + +.. important:: + If you use a squash commit merge strategy, it is recommended that you use the default + ``parse_squash_commits`` commit parser option to ensure that all the squashed commits are + parsed for version bumping and changelog generation. This is the default behavior in v10 across + all supported commit parsers. If you are upgrading, you likely will need to manually set this + option in your configuration file to ensure that the changelog is generated correctly. + + If you do not enable ``parse_squash_commits``, then version will only be determined by the + commit subject line and the changelog will only include the commit subject line as well. + + +.. _upgrade_v10-changelog_format-mask_initial_release: + +Changelog Format: Mask Initial Release +-------------------------------------- + +In v10, the default behavior for the changelog generation has been changed to mask the initial +release in the changelog. This means that the first release will not contain a break down of the +different types of changes (e.g., features, fixes, etc.), but instead it will just simply state +that this is the initial release. + + +.. _upgrade_v10-changelog_format-commit_parsing: + +Changelog Format: Commit Parsing +-------------------------------- + +We have made some minor changes to the commit parsing logic in *v10* to +separate out components of the commit message more clearly. You will find that the +:py:class:`ParsedCommit ` object's +descriptions list will no longer contain any Breaking Change footers, Release Notice footers, +PR/MR references, or Issue Closure footers. These were all previously extracted and placed +into their own attributes but were still included in the descriptions list. In *v10*, +the descriptions list will only contain the actual commit subject line and any additional +commit body text that is not part of the pre-defined footers. + +If you were relying on the descriptions list to contain these footers, you will need to +update your code and changelog templates to reference the specific attributes you want to use. + + +.. _upgrade_v10-default_config: + +Default Configuration Changes +----------------------------- + +The following table summarizes the changes to the default configuration values in v10: + +.. list-table:: + :widths: 5 55 20 20 + :header-rows: 1 + + * - # + - Configuration Option + - Previous Default Value + - New Default Value + + * - 1 + - :ref:`config-allow_zero_version` + - ``true`` + - ``false`` + + * - 2 + - :ref:`changelog.mode ` + - ``init`` + - ``update`` + + * - 3 + - :ref:`changelog.default_templates.mask_initial_release ` + - ``false`` + - ``true`` + + * - 4 + - :ref:`commit_parser_options.parse_squash_commits ` + - ``false`` + - ``true`` + + * - 5 + - :ref:`commit_parser_options.ignore_merge_commits ` + - ``false`` + - ``true`` + + +.. _upgrade_v10-deprecations: + +Deprecations & Removals +----------------------- + +No additional deprecations were made in *v10*, but the following are staged +for removal in v11: + +.. list-table:: Deprecated Features & Functions + :widths: 5 30 10 10 45 + :header-rows: 1 + + * - # + - Component + - Deprecated + - Planned Removal + - Notes + + * - 1 + - :ref:`GitHub Actions root_options ` + - v10.0.0 + - v10.0.0 + - Replaced with individual boolean flag inputs. See :ref:`above ` for details. + + * - 2 + - :ref:`Angular Commit Parser ` + - v9.19.0 + - v11.0.0 + - Replaced by the :ref:`Conventional Commit Parser `. + + * - 3 + - :ref:`Tag Commit Parser ` + - v9.12.0 + - v11.0.0 + - Replaced by the :ref:`Emoji Commit Parser `. + +.. note:: + For the most up-to-date information on the next version deprecations and removals, please + refer to the issue + `#1066 `_. diff --git a/docs/upgrading/index.rst b/docs/upgrading/index.rst index 6b7bcbdd6..4925d807e 100644 --- a/docs/upgrading/index.rst +++ b/docs/upgrading/index.rst @@ -22,5 +22,6 @@ versions of PSR to the latest version. :caption: Upgrade Guides :maxdepth: 1 + Upgrading to v10 <10-upgrade> Upgrading to v9 <09-upgrade> Upgrading to v8 <08-upgrade> From 4e52f4bba46e96a4762f97d306f15ae52c5cea1b Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 24 May 2025 15:49:30 -0600 Subject: [PATCH 090/120] docs: refactor documentation page navigation --- .gitignore | 2 +- docs/{ => api}/commands.rst | 4 +- docs/{ => concepts}/changelog_templates.rst | 2 +- docs/{ => concepts}/commit_parsing.rst | 4 +- docs/concepts/getting_started.rst | 391 ++++++++++++++++++ docs/concepts/index.rst | 17 + docs/concepts/installation.rst | 14 + docs/{ => concepts}/multibranch_releases.rst | 0 docs/{ => concepts}/strict_mode.rst | 0 docs/conf.py | 6 +- .../automatic-releases/cronjobs.rst | 4 +- .../automatic-releases/github-actions.rst | 0 .../automatic-releases/index.rst | 4 +- .../automatic-releases/travis.rst | 6 +- docs/{ => configuration}/configuration.rst | 4 +- docs/configuration/index.rst | 18 + ...ontributing.rst => contributing_guide.rst} | 0 docs/contributing/index.rst | 7 + docs/index.rst | 263 ++---------- docs/misc/psr_changelog.rst | 1 + docs/{ => misc}/troubleshooting.rst | 0 docs/psr_changelog.rst | 1 - 22 files changed, 513 insertions(+), 235 deletions(-) rename docs/{ => api}/commands.rst (99%) rename docs/{ => concepts}/changelog_templates.rst (99%) rename docs/{ => concepts}/commit_parsing.rst (99%) create mode 100644 docs/concepts/getting_started.rst create mode 100644 docs/concepts/index.rst create mode 100644 docs/concepts/installation.rst rename docs/{ => concepts}/multibranch_releases.rst (100%) rename docs/{ => concepts}/strict_mode.rst (100%) rename docs/{ => configuration}/automatic-releases/cronjobs.rst (96%) rename docs/{ => configuration}/automatic-releases/github-actions.rst (100%) rename docs/{ => configuration}/automatic-releases/index.rst (88%) rename docs/{ => configuration}/automatic-releases/travis.rst (95%) rename docs/{ => configuration}/configuration.rst (99%) create mode 100644 docs/configuration/index.rst rename docs/contributing/{contributing.rst => contributing_guide.rst} (100%) create mode 100644 docs/misc/psr_changelog.rst rename docs/{ => misc}/troubleshooting.rst (100%) delete mode 100644 docs/psr_changelog.rst diff --git a/.gitignore b/.gitignore index 7b02cda5b..db2c6f98d 100644 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,7 @@ coverage.xml # Sphinx documentation docs/_build/ -docs/api/ +docs/api/modules/ # PyBuilder target/ diff --git a/docs/commands.rst b/docs/api/commands.rst similarity index 99% rename from docs/commands.rst rename to docs/api/commands.rst index 30234e9e7..d99a40152 100644 --- a/docs/commands.rst +++ b/docs/api/commands.rst @@ -1,7 +1,7 @@ .. _commands: -Commands -======== +Command Line Interface (CLI) +============================ All commands accept a ``-h/--help`` option, which displays the help text for the command and exits immediately. diff --git a/docs/changelog_templates.rst b/docs/concepts/changelog_templates.rst similarity index 99% rename from docs/changelog_templates.rst rename to docs/concepts/changelog_templates.rst index e950db3d1..d42a210d7 100644 --- a/docs/changelog_templates.rst +++ b/docs/concepts/changelog_templates.rst @@ -248,7 +248,7 @@ Configuration Examples If identified or supported by the parser, the default changelog templates will include a separate section of breaking changes and additional release information. Refer to the -:ref:`commit parsing ` section to see how to write commit messages that +:ref:`commit parsing ` section to see how to write commit messages that will be properly parsed and displayed in these sections. diff --git a/docs/commit_parsing.rst b/docs/concepts/commit_parsing.rst similarity index 99% rename from docs/commit_parsing.rst rename to docs/concepts/commit_parsing.rst index 16340abeb..163927c39 100644 --- a/docs/commit_parsing.rst +++ b/docs/concepts/commit_parsing.rst @@ -1,4 +1,4 @@ -.. _commit-parsing: +.. _commit_parsing: Commit Parsing ============== @@ -641,7 +641,7 @@ should inherit from the The "options" class is used to validate the options which are configured in the repository, and to provide default values for these options where appropriate. -.. _commit-parsing-commit-parsers: +.. _commit_parsing-commit-parsers: Commit Parsers """""""""""""" diff --git a/docs/concepts/getting_started.rst b/docs/concepts/getting_started.rst new file mode 100644 index 000000000..63007948e --- /dev/null +++ b/docs/concepts/getting_started.rst @@ -0,0 +1,391 @@ +.. _getting-started-guide: + +Getting Started +=============== + +If you haven't done so already, install Python Semantic Release locally following the +:ref:`installation instructions `. + +If you are using a CI/CD service, you may not have to add Python Semantic Release to your +project's dependencies permanently, but for the duration of this guide for the initial +setup, you will need to have it installed locally. + + +Configuring PSR +--------------- + +Python Semantic Release ships with a reasonable default configuration but some aspects **MUST** be +customized to your project. To view the default configuration, run the following command: + +.. code-block:: bash + + semantic-release generate-config + +The output of the above command is the default configuration in TOML format without any modifications. +If this is fine for your project, then you do not need to configure anything else. + +PSR accepts overrides to the default configuration keys individually. If you don't define the +key-value pair in your configuration file, the default value will be used. + +By default, Python Semantic Release will look for configuration overrides in ``pyproject.toml`` under +the TOML table ``[tool.semantic_release]``. You may specify a different file using the +``-c/--config`` option, for example: + +.. code-block:: bash + + # In TOML format with top level table [semantic_release] + semantic-release -c releaserc.toml + + # In JSON format with top level object key {"semantic_release": {}} + semantic-release -c releaserc.json + +The easiest way to get started is to output the default configuration to a file, +delete keys you do not need to override, and then edit the remaining keys to suit your project. + +To set up in ``pyproject.toml``, run the following command: + +.. code-block:: bash + + # In file redirect in bash + semantic-release generate-config --pyproject >> pyproject.toml + + # Open your editor to edit the configuration + vim pyproject.toml + +.. seealso:: + - :ref:`cmd-generate-config` + - :ref:`configuration` + + +Configuring the Version Stamp Feature +''''''''''''''''''''''''''''''''''''' + +One of the best features of Python Semantic Release is the ability to automatically stamp the +new version number into your project files, so you don't have to manually update the version upon +each release. The version that is stamped is automatically determined by Python Semantic Release +from your commit messages which compliments automated versioning seamlessly. + +The most crucial version stamp is the one in your project metadata, which is used by +the Python Package Index (PyPI) and other package managers to identify the version of your package. + +For Python projects, this is typically the ``version`` field in your ``pyproject.toml`` file. First, +set up your project metadata with the base ``version`` value. If you are starting with a brand new project, +set ``project.version = "0.0.0"``. If you are working on an existing project, set it to the last +version number you released. Do not include any ``v`` prefix. + +.. important:: + The version number must be a valid SemVer version, which means it should follow the format + ``MAJOR.MINOR.PATCH`` (e.g., ``1.0.0``). Python Semantic Release does NOT support Canonical + version values defined in the `PEP 440`_ specification at this time. See + `Issue #455 `_ + for more details. Note that you can still define a SemVer version in the ``project.version`` + field, and when your build is generated, the build tool will automatically generate a PEP 440 + compliant version as long as you do **NOT** use a non-pep440 compliant pre-release token. + +.. _PEP 440: https://peps.python.org/pep-0440/ + +Your project metadata might look like this in ``pyproject.toml``:: + + [project] + name = "my-package" + version = "0.0.0" # Set this to the last released version or "0.0.0" for new projects + description = "A sample Python package" + +To configure PSR to automatically update this version number, you need to specify the file and value +to update in your configuration. Since ``pyproject.toml`` uses TOML format, you will add the +replacement specification to the ``tool.semantic_release.version_toml`` list. Update the following +configuration in your ``pyproject.toml`` file to include the version variable location: + +.. code-block:: toml + + [tool.semantic_release] + version_toml = ["pyproject.toml:project.version"] + + # Alternatively, if you are using poetry's 'version' key, then you would use: + version_toml = ["pyproject.toml:tool.poetry.version"] + +If you have other TOML files where you want to stamp the version, you can add them to the +``version_toml`` list as well. In the above example, there is an implicit assumption that +you only want the version as the raw number format. If you want to specify the full tag +value (e.g. v-prefixed version), then include ``:tf`` for "tag format" at the end of the +version variable specification. + +For non-TOML formatted files (such as JSON or YAML files), you can use the +:ref:`config-version_variables` configuration key instead. This feature uses an advanced +Regular Expression to find and replace the version variable in the specified files. + +For Python files, its much more effective to use ``importlib`` instead which will allow you to +dynamically import the version from your package metadata and not require your project to commit +the version number bump to the repository. For example, in your package's base ``__init__.py`` + +.. code-block:: python + + # my_package/__init__.py + from importlib.metadata import version as get_version + + __version__ = get_version(__package__) + # Note: __package__ must match your 'project.name' as defined in pyproject.toml + +.. seealso:: + - Configuration specification of :ref:`config-version_toml` + - Configuration specification of :ref:`config-version_variables` + + +Using PSR to Build your Project +''''''''''''''''''''''''''''''' + +PSR provides a convenient way to build your project artifacts as part of the versioning process +now that you have stamped the version into your project files. To enable this, you will need +to specify the build command in your configuration. This command will be executed after +the next version has been determined, and stamped into your files but before a release tag has +been created. + +To set up the build command, add the following to your ``pyproject.toml`` file: + +.. code-block:: toml + + [tool.semantic_release] + build_command = "python -m build --sdist --wheel ." + +.. seealso:: + - :ref:`config-build_command` - Configuration specification for the build command. + - :ref:`config-build_command_env` - Configuration specification for the build command environment variables. + + +Choosing a Commit Message Parser +'''''''''''''''''''''''''''''''' + +PSR uses commit messages to determine the type of version bump that should be applied +to your project. PSR supports multiple commit message parsing styles, allowing you to choose +the one that best fits your project's needs. Choose **one** of the supported commit parsers +defined in :ref:`commit_parsing`, or provide your own and configure it in your +``pyproject.toml`` file. + +Each commit parser has its own default configuration options so if you want to customize the parser +behavior, you will need to specify the parser options you want to override. + +.. code-block:: toml + + [tool.semantic_release] + commit_parser = "conventional" + + [tool.semantic_release.commit_parser_options] + minor_tags = ["feat"] + patch_tags = ["fix", "perf"] + parse_squash_commits = true + ignore_merge_commits = true + +.. important:: + Python Semantic Release does not currently support Monorepo projects. You will need to provide + a custom commit parser that is built for Monorepos. Follow the Monorepo-support progress in + `Issue #168 `_, + `Issue #614 `_, + and `PR #1143 `_. + + +Choosing your Changelog +''''''''''''''''''''''' + +Prior to creating a release, PSR will generate a changelog from the commit messages of your +project. The changelog is extremely customizable from the format to the content of each section. +PSR ships with a default changelog template that will be used if you do not provide custom +templates. The default should be sufficient for most projects and has its own set of configuration +options. + +For basic customization, you can choose either an traditional Markdown formatted changelog (default) +or if you want to integrate with a Sphinx Documentation project, you can use the +reStructuredText (RST) format. You can also choose the file name and location of where to write the +default changelog. + +To set your changelog location and changelog format, add the following to your ``pyproject.toml`` file: + +.. code-block:: toml + + [tool.semantic_release.changelog.default_templates] + changelog_file = "docs/source/CHANGELOG.rst" + output_format = "rst" # or "md" for Markdown format + +Secondly, the more important aspect of configuring your changelog is to define Commit Exclusion +Patterns or patterns that will be used to filter out commits from the changelog. PSR does **NOT** (yet) +come with a built-in set of exclusion patterns, so you will need to define them yourself. These commit +patterns should be in line with your project's commit parser configuration. + +To set commit exclusion patterns for a conventional commits parsers, add the following to your +``pyproject.toml`` file: + +.. code-block:: toml + + [tool.semantic_release.changelog.exclude_commit_patterns] + # Recommended patterns for conventional commits parser that is scope aware + exclude_commit_patterns = [ + '''chore(?:\([^)]*?\))?: .+''', + '''ci(?:\([^)]*?\))?: .+''', + '''refactor(?:\([^)]*?\))?: .+''', + '''style(?:\([^)]*?\))?: .+''', + '''test(?:\([^)]*?\))?: .+''', + '''build\((?!deps\): .+)''', + '''Initial [Cc]ommit.*''', + ] + +.. seealso:: + - :ref:`Changelog ` - Customize your changelog + - :ref:`changelog.mode ` - Choose the changelog mode ('update' or 'init') + - :ref:`changelog-templates-migrating-existing-changelog` + + +Defining your Release Branches +'''''''''''''''''''''''''''''' + +PSR provides a powerful feature to manage release types across multiple branches which can +allow you to configure your project to have different release branches for different purposes, +such as pre-release branches, beta branches, and your stable releases. + +.. note:: + Most projects that do **NOT** publish pre-releases will be fine with PSR's built-in default. + +To define an alpha pre-release branch when you are working on a fix or new feature, you can +add the following to your ``pyproject.toml`` file: + +.. code-block:: toml + + [tool.semantic_release.branches.alpha] + # Matches branches with the prefixes 'feat/', 'fix/', or 'perf/'. + match = "^(feat|fix|perf)/.+" + prerelease = true + prerelease_token = "alpha" + +Any time you execute ``semantic-release version`` on a branch with the prefix +``feat/``, ``fix/``, or ``perf/``, PSR will determine if a version bump is needed and if so, +the resulting version will be a pre-release version with the ``alpha`` token. For example, + ++-----------+--------------+-----------------+-------------------+ +| Branch | Version Bump | Current Version | Next Version | ++===========+==============+=================+===================+ +| main | Patch | ``1.0.0`` | ``1.0.1`` | ++-----------+--------------+-----------------+-------------------+ +| fix/bug-1 | Patch | ``1.0.0`` | ``1.0.1-alpha.1`` | ++-----------+--------------+-----------------+-------------------+ + +.. seealso:: + - :ref:`multibranch-releases` - Learn about multi-branch releases and how to configure them. + + +Configuring VCS Releases +'''''''''''''''''''''''' + +You can set up Python Semantic Release to create Releases in your remote version +control system, so you can publish assets and release notes for your project. + +In order to do so, you will need to place an authentication token in the +appropriate environment variable so that Python Semantic Release can authenticate +with the remote VCS to push tags, create releases, or upload files. + +GitHub (``GH_TOKEN``) +""""""""""""""""""""" + +For local publishing to GitHub, you should use a personal access token and +store it in your environment variables. Specify the name of the environment +variable in your configuration setting :ref:`remote.token `. +The default is ``GH_TOKEN``. + +To generate a token go to https://github.com/settings/tokens and click on +"Generate new token". + +For Personal Access Token (classic), you will need the ``repo`` scope to write +(ie. push) to the repository. + +For fine-grained Personal Access Tokens, you will need the `contents`__ +permission. + +__ https://docs.github.com/en/rest/authentication/permissions-required-for-fine-grained-personal-access-tokens#repository-permissions-for-contents + +GitLab (``GITLAB_TOKEN``) +""""""""""""""""""""""""" + +A personal access token from GitLab. This is used for authenticating when pushing +tags, publishing releases etc. This token should be stored in the ``GITLAB_TOKEN`` +environment variable. + +Gitea (``GITEA_TOKEN``) +""""""""""""""""""""""" + +A personal access token from Gitea. This token should be stored in the ``GITEA_TOKEN`` +environment variable. + +Bitbucket (``BITBUCKET_TOKEN``) +""""""""""""""""""""""""""""""" + +Bitbucket does not support uploading releases but can still benefit from automated tags +and changelogs. The user has three options to push changes to the repository: + +#. Use SSH keys. + +#. Use an `App Secret`_, store the secret in the ``BITBUCKET_TOKEN`` environment variable + and the username in ``BITBUCKET_USER``. + +#. Use an `Access Token`_ for the repository and store it in the ``BITBUCKET_TOKEN`` + environment variable. + +.. _App Secret: https://support.atlassian.com/bitbucket-cloud/docs/push-back-to-your-repository/#App-secret +.. _Access Token: https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens + +.. seealso:: + - :ref:`Changelog ` - customize your project's changelog. + + - :ref:`changelog-templates-custom_release_notes` - customize the published release notes + + - :ref:`version --vcs-release/--no-vcs-release ` - enable/disable VCS release + creation. + + +Testing your Configuration +-------------------------- + +It's time to test your configuration! + +.. code-block:: bash + + # 1. Run the command in no-operation mode to see what would happen + semantic-release -v --noop version + + # 2. If the output looks reasonable, try to run the command without any history changes + # '-vv' will give you verbose debug output, which is useful for troubleshooting + # commit parsing issues. + semantic-release -vv version --no-commit --no-tag + + # 3. Evaluate your repository to see the changes that were made but not committed + # - Check the version number in your pyproject.toml + # - Check the distribution files from the build command + # - Check the changelog file for the new release notes + + # 4. If everything looks good, make sure to commit/save your configuration changes + git add pyproject.toml + git commit -m "chore(config): configure Python Semantic Release" + + # 5. Now, try to run the release command with your history changes but without pushing + semantic-release -v version --no-push --no-vcs-release + + # 6. Check the result on your local repository + git status + git log --graph --decorate --all + + # 7a. If you are happy with the release history and resulting commit & tag, + # reverse your changes before trying the full release command. + git tag -d v0.0.1 # replace with the actual version you released + git reset --hard HEAD~1 + + # 7b. [Optional] Once you have configured a remote VCS token, try + # running the full release command to update the remote repository. + semantic-release version --push --vcs-release + # This is optional as you may not want a personal access token set up or make + # make the release permanent yet. + +.. seealso:: + - :ref:`cmd-version` + - :ref:`troubleshooting-verbosity` + +Configuring CI/CD +----------------- + +PSR is meant to help you release at speed! See our CI/CD Configuration guides under the +:ref:`automatic` section. diff --git a/docs/concepts/index.rst b/docs/concepts/index.rst new file mode 100644 index 000000000..efa3077a8 --- /dev/null +++ b/docs/concepts/index.rst @@ -0,0 +1,17 @@ +.. _concepts: + +Concepts +======== + +This section covers the core concepts of Python Semantic Release and how it +works. Understanding these concepts will help you effectively use Python +Semantic Release in your projects. + +.. toctree:: + :maxdepth: 1 + + getting_started + commit_parsing + changelog_templates + multibranch_releases + strict_mode diff --git a/docs/concepts/installation.rst b/docs/concepts/installation.rst new file mode 100644 index 000000000..3d99b13a5 --- /dev/null +++ b/docs/concepts/installation.rst @@ -0,0 +1,14 @@ +.. _installation: + +Installation +============ + +.. code-block:: bash + + python3 -m pip install python-semantic-release + semantic-release --help + +Python Semantic Release is also available from `conda-forge`_ or as a +:ref:`GitHub Action `. + +.. _conda-forge: https://anaconda.org/conda-forge/python-semantic-release diff --git a/docs/multibranch_releases.rst b/docs/concepts/multibranch_releases.rst similarity index 100% rename from docs/multibranch_releases.rst rename to docs/concepts/multibranch_releases.rst diff --git a/docs/strict_mode.rst b/docs/concepts/strict_mode.rst similarity index 100% rename from docs/strict_mode.rst rename to docs/concepts/strict_mode.rst diff --git a/docs/conf.py b/docs/conf.py index 7db91dd85..0d37bcf7a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,6 @@ import os import sys +from datetime import datetime, timezone sys.path.insert(0, os.path.abspath(".")) sys.path.insert(0, os.path.abspath("..")) @@ -24,7 +25,8 @@ source_suffix = ".rst" master_doc = "index" project = "python-semantic-release" -copyright = f"2024, {author_name}" # noqa: A001 +current_year = datetime.now(timezone.utc).astimezone().year +copyright = f"{current_year}, {author_name}" # noqa: A001 version = semantic_release.__version__ release = semantic_release.__version__ @@ -39,7 +41,7 @@ # -- Automatically run sphinx-apidoc -------------------------------------- docs_path = os.path.dirname(__file__) -apidoc_output_dir = os.path.join(docs_path, "api") +apidoc_output_dir = os.path.join(docs_path, "api", "modules") apidoc_module_dir = os.path.join(docs_path, "..", "src") apidoc_separate_modules = True apidoc_module_first = True diff --git a/docs/automatic-releases/cronjobs.rst b/docs/configuration/automatic-releases/cronjobs.rst similarity index 96% rename from docs/automatic-releases/cronjobs.rst rename to docs/configuration/automatic-releases/cronjobs.rst index c61e44ba8..0ecf6ca58 100644 --- a/docs/automatic-releases/cronjobs.rst +++ b/docs/configuration/automatic-releases/cronjobs.rst @@ -1,7 +1,7 @@ .. _cronjobs: -Publish with cronjobs -~~~~~~~~~~~~~~~~~~~~~ +Cron Job Publishing +=================== This is for you if for some reason you cannot publish from your CI or you would like releases to drop at a certain interval. Before you start, answer this: Are you sure you do not want a CI to diff --git a/docs/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst similarity index 100% rename from docs/automatic-releases/github-actions.rst rename to docs/configuration/automatic-releases/github-actions.rst diff --git a/docs/automatic-releases/index.rst b/docs/configuration/automatic-releases/index.rst similarity index 88% rename from docs/automatic-releases/index.rst rename to docs/configuration/automatic-releases/index.rst index c5e6d3453..3c3d8265b 100644 --- a/docs/automatic-releases/index.rst +++ b/docs/configuration/automatic-releases/index.rst @@ -1,13 +1,13 @@ .. _automatic: -Automatic Releases +Automated Releases ------------------ The key point with using this package is to automate your releases and stop worrying about version numbers. Different approaches to automatic releases and publishing with the help of this package can be found below. Using a CI is the recommended approach. -.. _automatic-guides: +.. _automated-release-guides: Guides ^^^^^^ diff --git a/docs/automatic-releases/travis.rst b/docs/configuration/automatic-releases/travis.rst similarity index 95% rename from docs/automatic-releases/travis.rst rename to docs/configuration/automatic-releases/travis.rst index 175f57447..5be380975 100644 --- a/docs/automatic-releases/travis.rst +++ b/docs/configuration/automatic-releases/travis.rst @@ -1,5 +1,7 @@ -Setting up python-semantic-release on Travis CI -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _travis_ci: + +Travis CI +========= This guide expects you to have activated the repository on Travis CI. If this is not the case, please refer to `Travis documentation`_ on how to do that. diff --git a/docs/configuration.rst b/docs/configuration/configuration.rst similarity index 99% rename from docs/configuration.rst rename to docs/configuration/configuration.rst index 34411e579..4d36852b7 100644 --- a/docs/configuration.rst +++ b/docs/configuration/configuration.rst @@ -1,4 +1,4 @@ -.. _configuration: +.. _config: Configuration ============= @@ -802,7 +802,7 @@ Built-in parsers: You can set any of the built-in parsers by their keyword but you can also specify your own commit parser in ``path/to/module_file.py:Class`` or ``module:Class`` form. -For more information see :ref:`commit-parsing`. +For more information see :ref:`commit_parsing`. **Default:** ``"conventional"`` diff --git a/docs/configuration/index.rst b/docs/configuration/index.rst new file mode 100644 index 000000000..3b5dade61 --- /dev/null +++ b/docs/configuration/index.rst @@ -0,0 +1,18 @@ +.. _configuration: + +Configuration +============= + +Python Semantic Release is highly configurable, allowing you to tailor it to your project's needs. It supports +various runtime environments and can be integrated with different CI/CD services. + +1. Check out the :ref:`Configuration Options ` to customize your release process. + +2. Configure your :ref:`CI/CD services ` to use Python Semantic Release. + +.. toctree:: + :maxdepth: 1 + :hidden: + + Configuration Options + automatic-releases/index diff --git a/docs/contributing/contributing.rst b/docs/contributing/contributing_guide.rst similarity index 100% rename from docs/contributing/contributing.rst rename to docs/contributing/contributing_guide.rst diff --git a/docs/contributing/index.rst b/docs/contributing/index.rst index 51d8945c2..164cc99a0 100644 --- a/docs/contributing/index.rst +++ b/docs/contributing/index.rst @@ -26,3 +26,10 @@ No matter how you choose to contribute, please check out our .. |contributors| image:: https://contributors-img.web.app/image?repo=python-semantic-release/python-semantic-release :target: https://github.com/python-semantic-release/python-semantic-release/graphs/contributors + + +.. toctree:: + :hidden: + :maxdepth: 1 + + Contributing Guide diff --git a/docs/index.rst b/docs/index.rst index 515885a39..c49f1d334 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,53 +1,55 @@ Python Semantic Release *********************** -|Ruff| |Test Status| |PyPI Version| |conda-forge version| |Read the Docs Status| |Pre-Commit Enabled| +|PyPI Version| |conda-forge version| |Last Release| |Monthly Downloads| |PSR License| |Issues| -Automatic Semantic Versioning for Python projects. This is a Python -implementation of `semantic-release`_ for JS by Stephan BΓΆnnemann. If -you find this topic interesting you should check out his `talk from -JSConf Budapest`_. +**Python Semantic Release (PSR)** provides an automated release mechanism +determined by SemVer and Commit Message Conventions for your Git projects. -The general idea is to be able to detect what the next version of the -project should be based on the commits. This tool will use that to -automate the whole release, upload to an artifact repository and post changelogs to -GitHub. You can run the tool on a CI service, or just run it locally. +The purpose of this project is to detect what the next version of the +project should be from parsing the latest commit messages. If the commit messages +describe changes that would require a major, minor or patch version bump, PSR +will automatically bump the version number accordingly. PSR, however, does not +stop there but will help automate the whole release process. It will update the +project code and distribution artifact, upload the artifact and post changelogs +to a remotely hosted Version Control System (VCS). -Installation -============ +The tool is designed to run inside of a CI/CD pipeline service, but it can +also be run locally. -:: +This project was originally inspired by the `semantic-release`_ project for JavaScript +by *Stephan BΓΆnnemann*, but the codebases have significantly deviated since then, as +PSR as driven towards the goal of providing flexible changelogs and simple initial setup. - python3 -m pip install python-semantic-release - semantic-release --help +.. include:: concepts/installation.rst -Python Semantic Release is also available from `conda-forge`_ or as a `GitHub Action`_. -Read more about the setup and configuration in our `getting started guide`_. +Read more about the setup and configuration in our :ref:`Getting Started Guide `. .. _semantic-release: https://github.com/semantic-release/semantic-release -.. _talk from JSConf Budapest: https://www.youtube.com/watch?v=tc2UgG5L7WM -.. _getting started guide: https://python-semantic-release.readthedocs.io/en/latest/#getting-started -.. _GitHub Action: https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html -.. _conda-forge: https://anaconda.org/conda-forge/python-semantic-release - -.. |Test Status| image:: https://img.shields.io/github/actions/workflow/status/python-semantic-release/python-semantic-release/cicd.yml?branch=master&label=Test%20Status&logo=github - :target: https://github.com/python-semantic-release/python-semantic-release/actions/workflows/cicd.yml - :alt: test-status + .. |PyPI Version| image:: https://img.shields.io/pypi/v/python-semantic-release?label=PyPI&logo=pypi :target: https://pypi.org/project/python-semantic-release/ :alt: pypi + .. |conda-forge Version| image:: https://img.shields.io/conda/vn/conda-forge/python-semantic-release?logo=anaconda :target: https://anaconda.org/conda-forge/python-semantic-release :alt: conda-forge -.. |Read the Docs Status| image:: https://img.shields.io/readthedocs/python-semantic-release?label=Read%20the%20Docs&logo=Read%20the%20Docs - :target: https://python-semantic-release.readthedocs.io/en/latest/ - :alt: docs -.. |Pre-Commit Enabled| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit - :target: https://github.com/pre-commit/pre-commit - :alt: pre-commit -.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json - :target: https://github.com/astral-sh/ruff - :alt: Ruff + +.. |Last Release| image:: https://img.shields.io/github/release-date/python-semantic-release/python-semantic-release?display_date=published_at + :target: https://github.com/python-semantic-release/python-semantic-release/releases/latest + :alt: GitHub Release Date + +.. |PSR License| image:: https://img.shields.io/pypi/l/python-semantic-release?color=blue + :target: https://github.com/python-semantic-release/python-semantic-release/blob/master/LICENSE + :alt: PyPI - License + +.. |Issues| image:: https://img.shields.io/github/issues/python-semantic-release/python-semantic-release + :target: https://github.com/python-semantic-release/python-semantic-release/issues + :alt: GitHub Issues + +.. |Monthly Downloads| image:: https://img.shields.io/pypi/dm/python-semantic-release + :target: https://pypistats.org/packages/python-semantic-release + :alt: PyPI - Downloads Documentation Contents @@ -56,194 +58,19 @@ Documentation Contents .. toctree:: :maxdepth: 1 - commands - Strict Mode - configuration - commit_parsing - Changelog Templates - Multibranch Releases - automatic-releases/index - troubleshooting + What's New + Concepts + CLI + configuration/index upgrading/index - Internal API - Changelog + misc/troubleshooting + API Contributing View on GitHub -Getting Started -=============== - -If you haven't done so already, install Python Semantic Release following the -instructions above. - -There is no strict requirement to have it installed locally if you intend on -:ref:`using a CI service `, however running with :ref:`cmd-main-option-noop` can be -useful to test your configuration. - -Generating your configuration ------------------------------ - -Python Semantic Release ships with a command-line interface, ``semantic-release``. You can -inspect the default configuration in your terminal by running - -``semantic-release generate-config`` - -You can also use the :ref:`-f/--format ` option to specify what format you would like this configuration -to be. The default is TOML, but JSON can also be used. - -You can append the configuration to your existing ``pyproject.toml`` file using a standard redirect, -for example: - -``semantic-release generate-config --pyproject >> pyproject.toml`` - -and then editing to your project's requirements. - -.. seealso:: - - :ref:`cmd-generate-config` - - :ref:`configuration` - - -Setting up version numbering ----------------------------- - -Create a variable set to the current version number. This could be anywhere in -your project, for example ``setup.py``:: - - from setuptools import setup - - __version__ = "0.0.0" - - setup( - name="my-package", - version=__version__, - # And so on... - ) - -Python Semantic Release can be configured using a TOML or JSON file; the default configuration file is -``pyproject.toml``, if you wish to use another file you will need to use the ``-c/--config`` option to -specify the file. - -Set :ref:`version_variables ` to a list, the only element of which should be the location of your -version variable inside any Python file, specified in standard ``module:attribute`` syntax: - -``pyproject.toml``:: - - [tool.semantic_release] - version_variables = ["setup.py:__version__"] - -.. seealso:: - - :ref:`configuration` - tailor Python Semantic Release to your project - -Setting up commit parsing -------------------------- - -We rely on commit messages to detect when a version bump is needed. -By default, Python Semantic Release uses the `Conventional Commits Specification`_ -to parse commit messages. You can find out more about this in :ref:`commit-parsing`. - -.. seealso:: - - :ref:`config-branches` - Adding configuration for releases from multiple branches. - - :ref:`commit_parser ` - use a different parser for commit messages. - For example, Python Semantic Release also ships with emoji and scipy-style parsers. - - :ref:`remote.type ` - specify the type of your remote VCS. - -.. _Conventional Commits Specification: https://www.conventionalcommits.org/en/v1.0.0 - -Setting up the changelog ------------------------- - -.. seealso:: - - :ref:`Changelog ` - Customize the changelog generated by Python Semantic Release. - - :ref:`changelog-templates-migrating-existing-changelog` - -.. _index-creating-vcs-releases: - -Creating VCS Releases ---------------------- - -You can set up Python Semantic Release to create Releases in your remote version -control system, so you can publish assets and release notes for your project. - -In order to do so, you will need to place an authentication token in the -appropriate environment variable so that Python Semantic Release can authenticate -with the remote VCS to push tags, create releases, or upload files. - -GitHub (``GH_TOKEN``) -""""""""""""""""""""" - -For local publishing to GitHub, you should use a personal access token and -store it in your environment variables. Specify the name of the environment -variable in your configuration setting :ref:`remote.token `. -The default is ``GH_TOKEN``. - -To generate a token go to https://github.com/settings/tokens and click on -"Generate new token". - -For Personal Access Token (classic), you will need the ``repo`` scope to write -(ie. push) to the repository. - -For fine-grained Personal Access Tokens, you will need the `contents`__ -permission. - -__ https://docs.github.com/en/rest/authentication/permissions-required-for-fine-grained-personal-access-tokens#repository-permissions-for-contents - -GitLab (``GITLAB_TOKEN``) -""""""""""""""""""""""""" - -A personal access token from GitLab. This is used for authenticating when pushing -tags, publishing releases etc. This token should be stored in the ``GITLAB_TOKEN`` -environment variable. - -Gitea (``GITEA_TOKEN``) -""""""""""""""""""""""" - -A personal access token from Gitea. This token should be stored in the ``GITEA_TOKEN`` -environment variable. - -Bitbucket (``BITBUCKET_TOKEN``) -""""""""""""""""""""""""""""""" - -Bitbucket does not support uploading releases but can still benefit from automated tags -and changelogs. The user has three options to push changes to the repository: - -#. Use SSH keys. -#. Use an `App Secret`_, store the secret in the ``BITBUCKET_TOKEN`` environment variable and the username in ``BITBUCKET_USER``. -#. Use an `Access Token`_ for the repository and store it in the ``BITBUCKET_TOKEN`` environment variable. - -.. _App Secret: https://support.atlassian.com/bitbucket-cloud/docs/push-back-to-your-repository/#App-secret -.. _Access Token: https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens - -.. seealso:: - - :ref:`Changelog ` - customize your project's changelog. - - :ref:`changelog-templates-custom_release_notes` - customize the published release notes - - :ref:`upload_to_vcs_release ` - - enable/disable uploading artifacts to VCS releases - - :ref:`version --vcs-release/--no-vcs-release ` - enable/disable VCS release - creation. - - `upload-to-gh-release`_, a GitHub Action for running ``semantic-release publish`` - -.. _upload-to-gh-release: https://github.com/python-semantic-release/upload-to-gh-release - -.. _running-from-setuppy: - -Running from setup.py ---------------------- - -Add the following hook to your ``setup.py`` and you will be able to run -``python setup.py `` as you would ``semantic-release ``:: - - try: - from semantic_release import setup_hook - setup_hook(sys.argv) - except ImportError: - pass - -.. note:: - Only the :ref:`version `, :ref:`publish `, and - :ref:`changelog ` commands may be invoked from setup.py in this way. +---- -Running on CI -------------- +.. _inline-getting-started-guide: -Getting a fully automated setup with releases from CI can be helpful for some -projects. See :ref:`automatic`. +.. include:: concepts/getting_started.rst + :start-after: .. _getting-started-guide: diff --git a/docs/misc/psr_changelog.rst b/docs/misc/psr_changelog.rst new file mode 100644 index 000000000..09929fe43 --- /dev/null +++ b/docs/misc/psr_changelog.rst @@ -0,0 +1 @@ +.. include:: ../../CHANGELOG.rst diff --git a/docs/troubleshooting.rst b/docs/misc/troubleshooting.rst similarity index 100% rename from docs/troubleshooting.rst rename to docs/misc/troubleshooting.rst diff --git a/docs/psr_changelog.rst b/docs/psr_changelog.rst deleted file mode 100644 index 565b0521d..000000000 --- a/docs/psr_changelog.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../CHANGELOG.rst From 58d4e7f8f056f465c387689e7514a33c07239ed5 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 1 Dec 2024 17:29:51 -0700 Subject: [PATCH 091/120] test(fixtures): update repo fixtures to include at least one issue closure reference --- tests/fixtures/repos/git_flow/repo_w_1_release_channel.py | 6 +++--- tests/fixtures/repos/git_flow/repo_w_2_release_channels.py | 6 +++--- tests/fixtures/repos/git_flow/repo_w_3_release_channels.py | 6 +++--- tests/fixtures/repos/git_flow/repo_w_4_release_channels.py | 6 +++--- tests/fixtures/repos/github_flow/repo_w_default_release.py | 6 +++--- tests/fixtures/repos/github_flow/repo_w_release_channels.py | 6 +++--- .../repos/trunk_based_dev/repo_w_dual_version_support.py | 6 +++--- .../repo_w_dual_version_support_w_prereleases.py | 6 +++--- tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py | 6 +++--- tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py | 6 +++--- tests/fixtures/repos/trunk_based_dev/repo_w_tags.py | 6 +++--- 11 files changed, 33 insertions(+), 33 deletions(-) diff --git a/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py b/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py index bdaf43d64..91eb5ffa5 100644 --- a/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py +++ b/tests/fixtures/repos/git_flow/repo_w_1_release_channel.py @@ -517,9 +517,9 @@ def _get_repo_from_defintion( "commits": convert_commit_specs_to_commit_defs( [ { - "conventional": "fix: correct a bug", - "emoji": ":bug: correct a bug", - "scipy": "BUG: correct a bug", + "conventional": "fix: correct a bug\n\nCloses: #123\n", + "emoji": ":bug: correct a bug\n\nCloses: #123\n", + "scipy": "BUG: correct a bug\n\nCloses: #123\n", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, diff --git a/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py index 537ef4b02..3d75af918 100644 --- a/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py @@ -603,9 +603,9 @@ def _get_repo_from_defintion( "commits": convert_commit_specs_to_commit_defs( [ { - "conventional": "fix(config): fixed configuration generation", - "emoji": ":bug: (config) fixed configuration generation", - "scipy": "MAINT:config: fixed configuration generation", + "conventional": "fix(config): fixed configuration generation\n\nCloses: #123", + "emoji": ":bug: (config) fixed configuration generation\n\nCloses: #123", + "scipy": "MAINT:config: fixed configuration generation\n\nCloses: #123", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, diff --git a/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py index bf49b27b5..d52b60f97 100644 --- a/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py @@ -728,9 +728,9 @@ def _get_repo_from_defintion( "commits": convert_commit_specs_to_commit_defs( [ { - "conventional": "fix(config): fix config option", - "emoji": ":bug: (config) fix config option", - "scipy": "BUG: config: fix config option", + "conventional": "fix(config): fix config option\n\nImplements: #123\n", + "emoji": ":bug: (config) fix config option\n\nImplements: #123\n", + "scipy": "BUG: config: fix config option\n\nImplements: #123\n", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, diff --git a/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py b/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py index 9062bd2b1..5bdb76d7d 100644 --- a/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py +++ b/tests/fixtures/repos/git_flow/repo_w_4_release_channels.py @@ -412,9 +412,9 @@ def _get_repo_from_defintion( "commits": convert_commit_specs_to_commit_defs( [ { - "conventional": "fix(cli): fix config cli command", - "emoji": ":bug: (cli) fix config cli command", - "scipy": "BUG:cli: fix config cli command", + "conventional": "fix(cli): fix config cli command\n\nCloses: #123\n", + "emoji": ":bug: (cli) fix config cli command\n\nCloses: #123\n", + "scipy": "BUG:cli: fix config cli command\n\nCloses: #123\n", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, diff --git a/tests/fixtures/repos/github_flow/repo_w_default_release.py b/tests/fixtures/repos/github_flow/repo_w_default_release.py index 25d30a271..61816f0bf 100644 --- a/tests/fixtures/repos/github_flow/repo_w_default_release.py +++ b/tests/fixtures/repos/github_flow/repo_w_default_release.py @@ -194,9 +194,9 @@ def _get_repo_from_definition( fix_branch_1_commits: Sequence[CommitSpec] = [ { - "conventional": "fix(cli): add missing text", - "emoji": ":bug: add missing text", - "scipy": "MAINT: add missing text", + "conventional": "fix(cli): add missing text\n\nResolves: #123\n", + "emoji": ":bug: add missing text\n\nResolves: #123\n", + "scipy": "MAINT: add missing text\n\nResolves: #123\n", "datetime": next(commit_timestamp_gen), }, ] diff --git a/tests/fixtures/repos/github_flow/repo_w_release_channels.py b/tests/fixtures/repos/github_flow/repo_w_release_channels.py index eec636dd4..87c57b609 100644 --- a/tests/fixtures/repos/github_flow/repo_w_release_channels.py +++ b/tests/fixtures/repos/github_flow/repo_w_release_channels.py @@ -217,9 +217,9 @@ def _get_repo_from_defintion( "commits": convert_commit_specs_to_commit_defs( [ { - "conventional": "fix: correct some text", - "emoji": ":bug: correct some text", - "scipy": "MAINT: correct some text", + "conventional": "fix: correct some text\n\nResolves: #123", + "emoji": ":bug: correct some text\n\nResolves: #123", + "scipy": "MAINT: correct some text\n\nResolves: #123", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py index f909c584b..008a7b6d6 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support.py @@ -314,9 +314,9 @@ def _get_repo_from_definition( "commits": convert_commit_specs_to_commit_defs( [ { - "conventional": "fix: correct critical bug", - "emoji": ":bug: correct critical bug", - "scipy": "MAINT: correct critical bug", + "conventional": "fix: correct critical bug\n\nResolves: #123\n", + "emoji": ":bug: correct critical bug\n\nResolves: #123\n", + "scipy": "MAINT: correct critical bug\n\nResolves: #123\n", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py index 88c07d6c3..4fb0d14c7 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_dual_version_support_w_prereleases.py @@ -315,9 +315,9 @@ def _get_repo_from_definition( "commits": convert_commit_specs_to_commit_defs( [ { - "conventional": "fix: correct critical bug", - "emoji": ":bug: correct critical bug", - "scipy": "MAINT: correct critical bug", + "conventional": "fix: correct critical bug\n\nResolves: #123\n", + "emoji": ":bug: correct critical bug\n\nResolves: #123\n", + "scipy": "MAINT: correct critical bug\n\nResolves: #123\n", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py index 37d9b0450..a1253c8e8 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py @@ -143,9 +143,9 @@ def _get_repo_from_definition( "include_in_changelog": True, }, { - "conventional": "fix: correct more text", - "emoji": ":bug: correct more text", - "scipy": "MAINT: correct more text", + "conventional": "fix: correct more text\n\nCloses: #123", + "emoji": ":bug: correct more text\n\nCloses: #123", + "scipy": "MAINT: correct more text\n\nCloses: #123", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py b/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py index a72ef1e20..57e46578f 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py @@ -189,9 +189,9 @@ def _get_repo_from_definition( "commits": convert_commit_specs_to_commit_defs( [ { - "conventional": "fix: correct some text", - "emoji": ":bug: correct some text", - "scipy": "MAINT: correct some text", + "conventional": "fix: correct some text\n\nfixes: #123\n", + "emoji": ":bug: correct some text\n\nfixes: #123\n", + "scipy": "MAINT: correct some text\n\nfixes: #123\n", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, diff --git a/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py b/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py index 40741bc18..b58a23865 100644 --- a/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py +++ b/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py @@ -191,9 +191,9 @@ def _get_repo_from_definition( "commits": convert_commit_specs_to_commit_defs( [ { - "conventional": "fix: correct some text", - "emoji": ":bug: correct some text", - "scipy": "MAINT: correct some text", + "conventional": "fix: correct some text\n\nResolves: #123\n", + "emoji": ":bug: correct some text\n\nResolves: #123\n", + "scipy": "MAINT: correct some text\n\nResolves: #123\n", "datetime": next(commit_timestamp_gen), "include_in_changelog": True, }, From 8d7b4c089144763aefd326573867afa6f55fcfd7 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 18 Dec 2024 01:33:19 -0700 Subject: [PATCH 092/120] test(cmd-version): update changelog test case for handling issue closure footers --- tests/e2e/cmd_version/test_version_changelog.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/e2e/cmd_version/test_version_changelog.py b/tests/e2e/cmd_version/test_version_changelog.py index 6dea5139b..a2ce88bf4 100644 --- a/tests/e2e/cmd_version/test_version_changelog.py +++ b/tests/e2e/cmd_version/test_version_changelog.py @@ -891,4 +891,6 @@ def test_version_updates_changelog_w_new_version_n_filtered_commit( # Evaluate assert_successful_exit_code(result, cli_cmd) assert expected_changelog_content == actual_content - assert expected_bump_message in actual_content + + for msg_part in expected_bump_message.split("\n\n"): + assert msg_part.capitalize() in actual_content From bd3e7bfa86d53a03f03ac419399847712c523b02 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 00:52:13 -0600 Subject: [PATCH 093/120] fix(cli): adjust verbosity parameter to enable silly-level logging --- src/semantic_release/cli/commands/main.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/semantic_release/cli/commands/main.py b/src/semantic_release/cli/commands/main.py index 08b893d70..c10d3f647 100644 --- a/src/semantic_release/cli/commands/main.py +++ b/src/semantic_release/cli/commands/main.py @@ -22,6 +22,12 @@ FORMAT = "%(message)s" +LOG_LEVELS = [ + SemanticReleaseLogLevels.WARNING, + SemanticReleaseLogLevels.INFO, + SemanticReleaseLogLevels.DEBUG, + SemanticReleaseLogLevels.SILLY, +] class Cli(click.MultiCommand): @@ -79,7 +85,7 @@ def get_command(self, _ctx: click.Context, name: str) -> click.Command | None: default=0, count=True, show_default=True, - type=click.IntRange(0, 2, clamp=True), + type=click.IntRange(0, len(LOG_LEVELS) - 1, clamp=True), ) @click.option( "--strict", @@ -107,14 +113,7 @@ def main( For more information, visit https://python-semantic-release.readthedocs.io/ """ - log_levels = [ - SemanticReleaseLogLevels.WARNING, - SemanticReleaseLogLevels.INFO, - SemanticReleaseLogLevels.DEBUG, - SemanticReleaseLogLevels.SILLY, - ] - - globals.log_level = log_levels[verbosity] + globals.log_level = LOG_LEVELS[verbosity] # Set up our pretty console formatter rich_handler = RichHandler( From bca51981f9503c97e7cac82fe1a5f44410285d64 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 01:13:11 -0600 Subject: [PATCH 094/120] chore(scripts): update doc script for new doc file locations --- scripts/bump_version_in_docs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/bump_version_in_docs.py b/scripts/bump_version_in_docs.py index f8c67331a..7c6104791 100644 --- a/scripts/bump_version_in_docs.py +++ b/scripts/bump_version_in_docs.py @@ -60,7 +60,8 @@ def envsubst(filepath: Path, version: str, release_tag: str) -> None: exit(1) update_github_actions_example( - DOCS_DIR / "automatic-releases" / "github-actions.rst", new_release_tag + DOCS_DIR / "configuration" / "automatic-releases" / "github-actions.rst", + new_release_tag, ) for doc_file in DOCS_DIR.rglob("*.rst"): From e06ee2876880c68ef90b11784bd2060adb9c5908 Mon Sep 17 00:00:00 2001 From: semantic-release Date: Sun, 25 May 2025 07:36:09 +0000 Subject: [PATCH 095/120] 10.0.0 Automatically generated by python-semantic-release --- CHANGELOG.rst | 357 ++++++++++++++++++ .../automatic-releases/github-actions.rst | 26 +- docs/configuration/configuration.rst | 8 +- pyproject.toml | 2 +- 4 files changed, 375 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5714ef3eb..61fa77784 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,363 @@ CHANGELOG ========= +.. _changelog-v10.0.0: + +v10.0.0 (2025-05-25) +==================== + +✨ Features +----------- + +* **cmd-version**: Enable ``version_variables`` version stamp of vars with double-equals + (`PR#1244`_, `080e4bc`_) + +* **parser-conventional**: Set parser to evaluate all squashed commits by default (`6fcdc99`_) + +* **parser-conventional**: Set parser to ignore merge commits by default (`59bf084`_) + +* **parser-emoji**: Set parser to evaluate all squashed commits by default (`514a922`_) + +* **parser-emoji**: Set parser to ignore merge commits by default (`8a51525`_) + +* **parser-scipy**: Set parser to evaluate all squashed commits by default (`634fffe`_) + +* **parser-scipy**: Set parser to ignore merge commits by default (`d4f128e`_) + +πŸͺ² Bug Fixes +------------ + +* **changelog-md**: Change to 1-line descriptions in markdown template, closes `#733`_ (`e7ac155`_) + +* **changelog-rst**: Change to 1-line descriptions in the default ReStructuredText template, closes + `#733`_ (`731466f`_) + +* **cli**: Adjust verbosity parameter to enable silly-level logging (`bd3e7bf`_) + +* **github-action**: Resolve command injection vulnerability in action script (`fb3da27`_) + +* **parser-conventional**: Remove breaking change footer messages from commit descriptions + (`b271cbb`_) + +* **parser-conventional**: Remove issue footer messages from commit descriptions (`b1bb0e5`_) + +* **parser-conventional**: Remove PR/MR references from commit subject line (`eed63fa`_) + +* **parser-conventional**: Remove release notice footer messages from commit descriptions + (`7e8dc13`_) + +* **parser-emoji**: Remove issue footer messages from commit descriptions (`b757603`_) + +* **parser-emoji**: Remove PR/MR references from commit subject line (`16465f1`_) + +* **parser-emoji**: Remove release notice footer messages from commit descriptions (`b6307cb`_) + +* **parser-scipy**: Remove issue footer messages from commit descriptions (`3cfee76`_) + +* **parser-scipy**: Remove PR/MR references from commit subject line (`da4140f`_) + +* **parser-scipy**: Remove release notice footer messages from commit descriptions (`58308e3`_) + +πŸ“– Documentation +---------------- + +* Refactor documentation page navigation (`4e52f4b`_) + +* **algorithm**: Remove out-of-date algorithm description (`6cd0fbe`_) + +* **commit-parsing**: Define limitation of revert commits with the scipy parser (`5310d0c`_) + +* **configuration**: Change default value for ``allow_zero_version`` in the description (`203d29d`_) + +* **configuration**: Change the default for the base changelog's ``mask_initial_release`` value + (`5fb02ab`_) + +* **configuration**: Change the default value for ``changelog.mode`` in the setting description + (`0bed906`_) + +* **configuration**: Update ``version_variables`` section to include double-equals operand support + (`PR#1244`_, `080e4bc`_) + +* **contributing**: Refactor contributing & contributors layout (`8bed5bc`_) + +* **github-actions**: Add reference to manual release workflow example (`6aad7f1`_) + +* **github-actions**: Change recommended workflow to separate release from deploy (`67b2ae0`_) + +* **github-actions**: Update ``python-semantic-release/publish-action`` parameter notes (`c4d45ec`_) + +* **github-actions**: Update PSR action parameter documenation (`a082896`_) + +* **upgrading**: Re-locate version upgrade guides into ``Upgrading PSR`` (`a5f5e04`_) + +* **upgrading-v10**: Added migration guide for v9 to v10 (`4ea92ec`_) + +βš™οΈ Build System +---------------- + +* **deps**: Prevent update to ``click@8.2.0`` (`PR#1245`_, `4aa6a6e`_) + +♻️ Refactoring +--------------- + +* **config**: Change ``allow_zero_version`` default to ``false`` (`c6b6eab`_) + +* **config**: Change ``changelog.default_templates.mask_initial_release`` default to ``true`` + (`0e114c3`_) + +* **config**: Change ``changelog.mode`` default to ``update`` (`7d39e76`_) + +πŸ’₯ Breaking Changes +------------------- + +* **changelog-md**: The default Markdown changelog template and release notes template will no + longer print out the entire commit message contents, instead, it will only print the commit + subject line. This comes to meet the high demand of better formatted changelogs and requests for + subject line only. Originally, it was a decision to not hide commit subjects that were included in + the commit body via the ``git merge --squash`` command and PSR did not have another alternative. + At this point, all the built-in parsers have the ability to parse squashed commits and separate + them out into their own entry on the changelog. Therefore, the default template no longer needs to + write out the full commit body. See the commit parser options if you want to enable/disable + parsing squash commits. + +* **changelog-rst**: The default ReStructured changelog template will no longer print out the entire + commit message contents, instead, it will only print the commit subject line. This comes to meet + the high demand of better formatted changelogs and requests for subject line only. Originally, it + was a decision to not hide commit subjects that were included in the commit body via the ``git + merge --squash`` command and PSR did not have another alternative. At this point, all the built-in + parsers have the ability to parse squashed commits and separate them out into their own entry on + the changelog. Therefore, the default template no longer needs to write out the full commit body. + See the commit parser options if you want to enable/disable parsing squash commits. + +* **config**: This release switches the ``allow_zero_version`` default to ``false``. This change is + to encourage less ``0.x`` releases as the default but rather allow the experienced developer to + choose when ``0.x`` is appropriate. There are way too many projects in the ecosystems that never + leave ``0.x`` and that is problematic for the industry tools that help auto-update based on + SemVer. We should strive for publishing usable tools and maintaining good forethought for when + compatibility must break. If your configuration already sets the ``allow_zero_version`` value, + this change will have no effect on your project. If you want to use ``0.x`` versions, from the + start then change ``allow_zero_version`` to ``true`` in your configuration. + +* **config**: This release switches the ``changelog.default_templates.mask_initial_release`` default + to ``true``. This change is intended to toggle better recommended outputs of the default + changelog. Conceptually, the very first release is hard to describe--one can only provide new + features as nothing exists yet for the end user. No changelog should be written as there is no + start point to compare the "changes" to. The recommendation instead is to only list a simple + message as ``Initial Release``. This is now the default for PSR when providing the very first + release (no pre-existing tags) in the changelog and release notes. If your configuration already + sets the ``changelog.default_templates.mask_initial_release`` value, then this change will have no + effect on your project. If you do NOT want to mask the first release information, then set + ``changelog.default_templates.mask_initial_release`` to ``false`` in your configuration. + +* **config**: This release switches the ``changelog.mode`` default to ``update``. In this mode, if a + changelog exists, PSR will update the changelog **IF AND ONLY IF** the configured insertion flag + exists in the changelog. The Changelog output will remain unchanged if no insertion flag exists. + The insertion flag may be configured with the ``changelog.insertion_flag`` setting. When upgrading + to ``v10``, you must add the insertion flag manually or you can just delete the changelog file and + run PSR's changelog generation and it will rebuild the changelog (similar to init mode) but it + will add the insertion flag. If your configuration already sets the ``changelog.mode`` value, then + this change will have no effect on your project. If you would rather the changelog be generated + from scratch every release, than set the ``changelog.mode`` value to ``init`` in your + configuration. + +* **github-action**: The ``root_options`` action input parameter has been removed because it created + a command injection vulernability for arbitrary code to execute within the container context of + the GitHub action if a command injection code was provided as part of the ``root_options`` + parameter string. To eliminate the vulnerability, each relevant option that can be provided to + ``semantic-release`` has been individually added as its own parameter and will be processed + individually to prevent command injection. Please review our `Github Actions Configuration`__ page + on the Python Semantic Release Documentation website to review the newly available configuration + options that replace the ``root_options`` parameter. + +* **parser-conventional**: Any breaking change footer messages that the conventional commit parser + detects will now be removed from the ``commit.descriptions[]`` list but maintained in and only in + the ``commit.breaking_descriptions[]`` list. Previously, the descriptions included all text from + the commit message but that was redundant as the default changelog now handles breaking change + footers in its own section. + +* **parser-conventional**: Any issue resolution footers that the parser detects will now be removed + from the ``commit.descriptions[]`` list. Previously, the descriptions included all text from the + commit message but now that the parser pulls out the issue numbers the numbers will be included in + the ``commit.linked_issues`` tuple for user extraction in any changelog generation. + +* **parser-conventional**: Any release notice footer messages that the commit parser detects will + now be removed from the ``commit.descriptions[]`` list but maintained in and only in the + ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message + but that was redundant as the default changelog now handles release notice footers in its own + section. + +* **parser-conventional**: Generally, a pull request or merge request number reference is included + in the subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks + for this reference and extracts it into the ``commit.linked_merge_request`` and the + ``commit.linked_pull_request`` attributes of a commit object. Since this is now pulled out + individually, it is cleaner to remove this from the first line of the ``commit.descriptions`` list + (ie. the subject line) so that changelog macros do not have to replace the text but instead only + append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator + (`#` or ``!``). + +* **parser-conventional**: The configuration setting ``commit_parser_options.ignore_merge_commits`` + is now set to ``true`` by default. The feature to ignore squash commits was introduced in + ``v9.18.0`` and was originally set to ``false`` to prevent unexpected results on a non-breaking + update. The ignore merge commits feature prevents additional unnecessary processing on a commit + message that likely will not match a commit message syntax. Most merge commits are syntactically + pre-defined by Git or Remote Version Control System (ex. GitHub, etc.) and do not follow a commit + convention (nor should they). The larger issue with merge commits is that they ultimately are a + full copy of all the changes that were previously created and committed. The merge commit itself + ensures that the previous commit tree is maintained in history, therefore the commit message + always exists. If merge commits are parsed, it generally creates duplicate messages that will end + up in your changelog, which is less than desired in most cases. If you have previously used the + ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will + want this setting set to ``true`` to improve parsing speed. You can also now remove the merge + commit exclude pattern from the list as well to improve parsing speed. If this functionality is + not desired, you will need to update your configuration to change the new setting to ``false``. + +* **parser-conventional**: The configuration setting ``commit_parser_options.parse_squash_commits`` + is now set to ``true`` by default. The feature to parse squash commits was introduced in + ``v9.17.0`` and was originally set to ``false`` to prevent unexpected results on a non-breaking + update. The parse squash commits feature attempts to find additional commits of the same commit + type within the body of a single commit message. When squash commits are found, Python Semantic + Release will separate out each commit into its own artificial commit object and parse them + individually. This potentially can change the resulting version bump if a larger bump was detected + within the squashed components. It also allows for the changelog and release notes to separately + order and display each commit as originally written. If this is not desired, you will need to + update your configuration to change the new setting to ``false``. + +* **parser-emoji**: Any issue resolution footers that the parser detects will now be removed from + the ``commit.descriptions[]`` list. Previously, the descriptions included all text from the commit + message but now that the parser pulls out the issue numbers the numbers will be included in the + ``commit.linked_issues`` tuple for user extraction in any changelog generation. + +* **parser-emoji**: Any release notice footer messages that the emoji commit parser detects will now + be removed from the ``commit.descriptions[]`` list but maintained in and only in the + ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message + but that was redundant as the default changelog now handles release notice footers in its own + section. + +* **parser-emoji**: Generally, a pull request or merge request number reference is included in the + subject line at the end within parentheses on some common VCS's (e.g. GitHub). PSR now looks for + these references and extract it into the ``commit.linked_merge_request`` field of a commit object. + Since this is now pulled out individually, it is cleaner to remove this from the first line of the + ``commit.descriptions`` list (ie. the subject line) so that changelog macros do not have to + replace the text but instead only append a PR/MR link to the end of the line. The reference will + maintain the PR/MR prefix indicator (e.g. ``#`` or ``!``). + +* **parser-emoji**: The configuration setting ``commit_parser_options.ignore_merge_commits`` is now + set to ``true`` by default. The feature to ignore squash commits was introduced in ``v9.18.0`` and + was originally set to ``false`` to prevent unexpected results on a non-breaking update. The ignore + merge commits feature prevents additional unnecessary processing on a commit message that likely + will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or + Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should + they). The larger issue with merge commits is that they ultimately are a full copy of all the + changes that were previously created and committed. The merge commit itself ensures that the + previous commit tree is maintained in history, therefore the commit message always exists. If + merge commits are parsed, it generally creates duplicate messages that will end up in your + changelog, which is less than desired in most cases. If you have previously used the + ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will + want this setting set to ``true`` to improve parsing speed. You can also now remove the merge + commit exclude pattern from the list as well to improve parsing speed. If this functionality is + not desired, you will need to update your configuration to change the new setting to ``false``. + +* **parser-emoji**: The configuration setting ``commit_parser_options.parse_squash_commits`` is now + set to ``true`` by default. The feature to parse squash commits was introduced in ``v9.17.0`` and + was originally set to ``false`` to prevent unexpected results on a non-breaking update. The parse + squash commits feature attempts to find additional commits of the same commit type within the body + of a single commit message. When squash commits are found, Python Semantic Release will separate + out each commit into its own artificial commit object and parse them individually. This + potentially can change the resulting version bump if a larger bump was detected within the + squashed components. It also allows for the changelog and release notes to separately order and + display each commit as originally written. If this is not desired, you will need to update your + configuration to change the new setting to ``false``. + +* **parser-scipy**: Any issue resolution footers that the parser detects will now be removed from + the commit.descriptions[] list. Previously, the descriptions included all text from the commit + message but now that the parser pulls out the issue numbers the numbers will be included in the + commit.linked_issues tuple for user extraction in any changelog generation. + +* **parser-scipy**: Any release notice footer messages that the commit parser detects will now be + removed from the ``commit.descriptions[]`` list but maintained in and only in the + ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message + but that was redundant as the default changelog now handles release notice footers in its own + section. + +* **parser-scipy**: Generally, a pull request or merge request number reference is included in the + subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks for + this reference and extracts it into the ``commit.linked_merge_request`` and the + ``commit.linked_pull_request`` attributes of a commit object. Since this is now pulled out + individually, it is cleaner to remove this from the first line of the ``commit.descriptions`` list + (ie. the subject line) so that changelog macros do not have to replace the text but instead only + append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator + (`#` or ``!``). + +* **parser-scipy**: The configuration setting ``commit_parser_options.ignore_merge_commits`` is now + set to ``true`` by default. The feature to ignore squash commits was introduced in ``v9.18.0`` and + was originally set to ``false`` to prevent unexpected results on a non-breaking update. The ignore + merge commits feature prevents additional unnecessary processing on a commit message that likely + will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or + Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should + they). The larger issue with merge commits is that they ultimately are a full copy of all the + changes that were previously created and committed. The merge commit itself ensures that the + previous commit tree is maintained in history, therefore the commit message always exists. If + merge commits are parsed, it generally creates duplicate messages that will end up in your + changelog, which is less than desired in most cases. If you have previously used the + ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will + want this setting set to ``true`` to improve parsing speed. You can also now remove the merge + commit exclude pattern from the list as well to improve parsing speed. If this functionality is + not desired, you will need to update your configuration to change the new setting to ``false``. + +* **parser-scipy**: The configuration setting ``commit_parser_options.parse_squash_commits`` is now + set to ``true`` by default. The feature to parse squash commits was introduced in ``v9.17.0`` and + was originally set to ``false`` to prevent unexpected results on a non-breaking update. The parse + squash commits feature attempts to find additional commits of the same commit type within the body + of a single commit message. When squash commits are found, Python Semantic Release will separate + out each commit into its own artificial commit object and parse them individually. This + potentially can change the resulting version bump if a larger bump was detected within the + squashed components. It also allows for the changelog and release notes to separately order and + display each commit as originally written. If this is not desired, you will need to update your + configuration to change the new setting to ``false``. + +.. _#733: https://github.com/python-semantic-release/python-semantic-release/issues/733 +.. _080e4bc: https://github.com/python-semantic-release/python-semantic-release/commit/080e4bcb14048a2dd10445546a7ee3159b3ab85c +.. _0bed906: https://github.com/python-semantic-release/python-semantic-release/commit/0bed9069df67ae806ad0a15f8434ac4efcc6ba31 +.. _0e114c3: https://github.com/python-semantic-release/python-semantic-release/commit/0e114c3458a24b87bfd2d6b0cd3f5cfdc9497084 +.. _16465f1: https://github.com/python-semantic-release/python-semantic-release/commit/16465f133386b09627d311727a6f8d24dd8f174f +.. _203d29d: https://github.com/python-semantic-release/python-semantic-release/commit/203d29d9d6b8e862eabe2f99dbd27eabf04e75e2 +.. _3cfee76: https://github.com/python-semantic-release/python-semantic-release/commit/3cfee76032662bda6fbdd7e2585193213e4f9da2 +.. _4aa6a6e: https://github.com/python-semantic-release/python-semantic-release/commit/4aa6a6edbff75889e09f32f7cba52cb90c9fb626 +.. _4e52f4b: https://github.com/python-semantic-release/python-semantic-release/commit/4e52f4bba46e96a4762f97d306f15ae52c5cea1b +.. _4ea92ec: https://github.com/python-semantic-release/python-semantic-release/commit/4ea92ec34dcd45d8cbab24e38e55289617b2d728 +.. _514a922: https://github.com/python-semantic-release/python-semantic-release/commit/514a922fa87721e2500062dcae841bedd84dc1fe +.. _5310d0c: https://github.com/python-semantic-release/python-semantic-release/commit/5310d0c700840538f27874394b9964bf09cd69b1 +.. _58308e3: https://github.com/python-semantic-release/python-semantic-release/commit/58308e31bb6306aac3a985af01eb779dc923d3f0 +.. _59bf084: https://github.com/python-semantic-release/python-semantic-release/commit/59bf08440a15269afaac81d78dd03ee418f9fd6b +.. _5fb02ab: https://github.com/python-semantic-release/python-semantic-release/commit/5fb02ab6e3b8278ecbf92ed35083ffb595bc19b8 +.. _634fffe: https://github.com/python-semantic-release/python-semantic-release/commit/634fffea29157e9b6305b21802c78ac245454265 +.. _67b2ae0: https://github.com/python-semantic-release/python-semantic-release/commit/67b2ae0050cce540a4126fe280cca6dc4bcf5d3f +.. _6aad7f1: https://github.com/python-semantic-release/python-semantic-release/commit/6aad7f17e64fb4717ddd7a9e94d2a730be6a3bd9 +.. _6cd0fbe: https://github.com/python-semantic-release/python-semantic-release/commit/6cd0fbeb44e16d394c210216c7099afa51f5a4a3 +.. _6fcdc99: https://github.com/python-semantic-release/python-semantic-release/commit/6fcdc99e9462b1186ea9488fc14e4e18f8c7fdb3 +.. _731466f: https://github.com/python-semantic-release/python-semantic-release/commit/731466fec4e06fe71f6c4addd4ae2ec2182ae9c1 +.. _7d39e76: https://github.com/python-semantic-release/python-semantic-release/commit/7d39e7675f859463b54751d59957b869d5d8395c +.. _7e8dc13: https://github.com/python-semantic-release/python-semantic-release/commit/7e8dc13c0b048a95d01f7aecfbe4eeedcddec9a4 +.. _8a51525: https://github.com/python-semantic-release/python-semantic-release/commit/8a5152573b9175f01be06d0c4531ea0ca4de8dd4 +.. _8bed5bc: https://github.com/python-semantic-release/python-semantic-release/commit/8bed5bcca4a5759af0e3fb24eadf14aa4e4f53c9 +.. _a082896: https://github.com/python-semantic-release/python-semantic-release/commit/a08289693085153effdafe3c6ff235a1777bb1fa +.. _a5f5e04: https://github.com/python-semantic-release/python-semantic-release/commit/a5f5e042ae9af909ee9e3ddf57c78adbc92ce378 +.. _b1bb0e5: https://github.com/python-semantic-release/python-semantic-release/commit/b1bb0e55910715754eebef6cb5b21ebed5ee8d68 +.. _b271cbb: https://github.com/python-semantic-release/python-semantic-release/commit/b271cbb2d3e8b86d07d1358b2e7424ccff6ae186 +.. _b6307cb: https://github.com/python-semantic-release/python-semantic-release/commit/b6307cb649043bbcc7ad9f15ac5ac6728914f443 +.. _b757603: https://github.com/python-semantic-release/python-semantic-release/commit/b757603e77ebe26d8a14758d78fd21163a9059b2 +.. _bd3e7bf: https://github.com/python-semantic-release/python-semantic-release/commit/bd3e7bfa86d53a03f03ac419399847712c523b02 +.. _c4d45ec: https://github.com/python-semantic-release/python-semantic-release/commit/c4d45ec46dfa81f645c25ea18ffffe9635922603 +.. _c6b6eab: https://github.com/python-semantic-release/python-semantic-release/commit/c6b6eabbfe100d2c741620eb3fa12a382531fa94 +.. _d4f128e: https://github.com/python-semantic-release/python-semantic-release/commit/d4f128e75e33256c0163fbb475c7c41e18f65147 +.. _da4140f: https://github.com/python-semantic-release/python-semantic-release/commit/da4140f3e3a2ed03c05064f35561b4584f517105 +.. _e7ac155: https://github.com/python-semantic-release/python-semantic-release/commit/e7ac155a91fc2e735d3cbf9b66fb4e5ff40a1466 +.. _eed63fa: https://github.com/python-semantic-release/python-semantic-release/commit/eed63fa9f6e762f55700fc85ef3ebdc0d3144f21 +.. _fb3da27: https://github.com/python-semantic-release/python-semantic-release/commit/fb3da27650ff15bcdb3b7badc919bd8a9a73238d +.. _PR#1244: https://github.com/python-semantic-release/python-semantic-release/pull/1244 +.. _PR#1245: https://github.com/python-semantic-release/python-semantic-release/pull/1245 + + .. _changelog-v9.21.1: v9.21.1 (2025-05-05) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index 94aafaf62..6e90dfc25 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -371,8 +371,8 @@ to the remote repository. This option is equivalent to adding either ``--push`` """""""""""""""" .. important:: - This option has been removed in version $NEW_VERSION and newer because of a - command injection vulnerability. Please update as to $NEW_RELEASE_TAG as soon + This option has been removed in version 10.0.0 and newer because of a + command injection vulnerability. Please update as to v10.0.0 as soon as possible. Additional options for the main ``semantic-release`` command, which will come @@ -382,7 +382,7 @@ before the :ref:`version ` subcommand. .. code:: yaml - - uses: python-semantic-release/python-semantic-release@v9.21.1 + - uses: python-semantic-release/python-semantic-release@v10.0.0 with: root_options: "-vv --noop" @@ -688,8 +688,8 @@ This is useful for testing the action without actually publishing anything. """""""""""""""" .. important:: - This option has been removed in version $NEW_VERSION and newer because of a - command injection vulnerability. Please update as to $NEW_RELEASE_TAG as soon + This option has been removed in version 10.0.0 and newer because of a + command injection vulnerability. Please update as to v10.0.0 as soon as possible. Additional options for the main ``semantic-release`` command, which will come @@ -699,7 +699,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v9.21.1 + - uses: python-semantic-release/publish-action@v10.0.0 with: root_options: "-vv --noop" @@ -873,14 +873,14 @@ to the GitHub Release Assets as well. - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v9.21.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.1 + uses: python-semantic-release/publish-action@v10.0.0 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -979,7 +979,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.21.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -1038,14 +1038,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v9.21.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: directory: ${{ env.SUBMODULE_1_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release submodule 2 id: release-submod-2 - uses: python-semantic-release/python-semantic-release@v9.21.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1057,7 +1057,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.1 + uses: python-semantic-release/publish-action@v10.0.0 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -1065,7 +1065,7 @@ Publish Action. tag: ${{ steps.release-submod-1.outputs.tag }} - name: Publish | Upload package 2 to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.1 + uses: python-semantic-release/publish-action@v10.0.0 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/docs/configuration/configuration.rst b/docs/configuration/configuration.rst index 4d36852b7..89bfc7862 100644 --- a/docs/configuration/configuration.rst +++ b/docs/configuration/configuration.rst @@ -142,7 +142,7 @@ version to be ``1.0.0``, regardless of patch, minor, or major change level. Additionally, when ``allow_zero_version`` is set to ``false``, the :ref:`config-major_on_zero` setting is ignored. -*Default changed to ``false`` in $NEW_VERSION* +*Default changed to ``false`` in 10.0.0* **Default:** ``false`` @@ -390,7 +390,7 @@ is there to document? The message details can be found in the ``first_release.md.j2`` and ``first_release.rst.j2`` templates of the default changelog template directory. -*Default changed to ``true`` in $NEW_VERSION.* +*Default changed to ``true`` in 10.0.0.* **Default:** ``true`` @@ -664,7 +664,7 @@ The patterns in this list are treated as regular expressions. ``mode`` ******** -*Introduced in v9.10.0. Default changed to `update` in $NEW_VERSION.* +*Introduced in v9.10.0. Default changed to `update` in 10.0.0.* **Type:** ``Literal["init", "update"]`` @@ -1351,7 +1351,7 @@ The regular expression generated from the ``version_variables`` definition will: 2. The variable name defined by ``variable`` and the version must be separated by an operand symbol (``=``, ``:``, ``:=``, or ``@``). Whitespace is optional around - the symbol. As of $NEW_VERSION, a double-equals (``==``) operator is also supported + the symbol. As of 10.0.0, a double-equals (``==``) operator is also supported as a valid operand symbol. 3. The value of the variable must match a `SemVer`_ regular expression and can be diff --git a/pyproject.toml b/pyproject.toml index addaf110e..ec0bc1f8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "9.21.1" +version = "10.0.0" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } From 7338fcfb1fc6686738762ba332c30143b7df2208 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 01:42:17 -0600 Subject: [PATCH 096/120] Revert "10.0.0" This reverts commit e06ee2876880c68ef90b11784bd2060adb9c5908. Revert commit because release & deploy process failed in CI. Must revert. --- CHANGELOG.rst | 357 ------------------ .../automatic-releases/github-actions.rst | 26 +- docs/configuration/configuration.rst | 8 +- pyproject.toml | 2 +- 4 files changed, 18 insertions(+), 375 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 61fa77784..5714ef3eb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,363 +4,6 @@ CHANGELOG ========= -.. _changelog-v10.0.0: - -v10.0.0 (2025-05-25) -==================== - -✨ Features ------------ - -* **cmd-version**: Enable ``version_variables`` version stamp of vars with double-equals - (`PR#1244`_, `080e4bc`_) - -* **parser-conventional**: Set parser to evaluate all squashed commits by default (`6fcdc99`_) - -* **parser-conventional**: Set parser to ignore merge commits by default (`59bf084`_) - -* **parser-emoji**: Set parser to evaluate all squashed commits by default (`514a922`_) - -* **parser-emoji**: Set parser to ignore merge commits by default (`8a51525`_) - -* **parser-scipy**: Set parser to evaluate all squashed commits by default (`634fffe`_) - -* **parser-scipy**: Set parser to ignore merge commits by default (`d4f128e`_) - -πŸͺ² Bug Fixes ------------- - -* **changelog-md**: Change to 1-line descriptions in markdown template, closes `#733`_ (`e7ac155`_) - -* **changelog-rst**: Change to 1-line descriptions in the default ReStructuredText template, closes - `#733`_ (`731466f`_) - -* **cli**: Adjust verbosity parameter to enable silly-level logging (`bd3e7bf`_) - -* **github-action**: Resolve command injection vulnerability in action script (`fb3da27`_) - -* **parser-conventional**: Remove breaking change footer messages from commit descriptions - (`b271cbb`_) - -* **parser-conventional**: Remove issue footer messages from commit descriptions (`b1bb0e5`_) - -* **parser-conventional**: Remove PR/MR references from commit subject line (`eed63fa`_) - -* **parser-conventional**: Remove release notice footer messages from commit descriptions - (`7e8dc13`_) - -* **parser-emoji**: Remove issue footer messages from commit descriptions (`b757603`_) - -* **parser-emoji**: Remove PR/MR references from commit subject line (`16465f1`_) - -* **parser-emoji**: Remove release notice footer messages from commit descriptions (`b6307cb`_) - -* **parser-scipy**: Remove issue footer messages from commit descriptions (`3cfee76`_) - -* **parser-scipy**: Remove PR/MR references from commit subject line (`da4140f`_) - -* **parser-scipy**: Remove release notice footer messages from commit descriptions (`58308e3`_) - -πŸ“– Documentation ----------------- - -* Refactor documentation page navigation (`4e52f4b`_) - -* **algorithm**: Remove out-of-date algorithm description (`6cd0fbe`_) - -* **commit-parsing**: Define limitation of revert commits with the scipy parser (`5310d0c`_) - -* **configuration**: Change default value for ``allow_zero_version`` in the description (`203d29d`_) - -* **configuration**: Change the default for the base changelog's ``mask_initial_release`` value - (`5fb02ab`_) - -* **configuration**: Change the default value for ``changelog.mode`` in the setting description - (`0bed906`_) - -* **configuration**: Update ``version_variables`` section to include double-equals operand support - (`PR#1244`_, `080e4bc`_) - -* **contributing**: Refactor contributing & contributors layout (`8bed5bc`_) - -* **github-actions**: Add reference to manual release workflow example (`6aad7f1`_) - -* **github-actions**: Change recommended workflow to separate release from deploy (`67b2ae0`_) - -* **github-actions**: Update ``python-semantic-release/publish-action`` parameter notes (`c4d45ec`_) - -* **github-actions**: Update PSR action parameter documenation (`a082896`_) - -* **upgrading**: Re-locate version upgrade guides into ``Upgrading PSR`` (`a5f5e04`_) - -* **upgrading-v10**: Added migration guide for v9 to v10 (`4ea92ec`_) - -βš™οΈ Build System ----------------- - -* **deps**: Prevent update to ``click@8.2.0`` (`PR#1245`_, `4aa6a6e`_) - -♻️ Refactoring ---------------- - -* **config**: Change ``allow_zero_version`` default to ``false`` (`c6b6eab`_) - -* **config**: Change ``changelog.default_templates.mask_initial_release`` default to ``true`` - (`0e114c3`_) - -* **config**: Change ``changelog.mode`` default to ``update`` (`7d39e76`_) - -πŸ’₯ Breaking Changes -------------------- - -* **changelog-md**: The default Markdown changelog template and release notes template will no - longer print out the entire commit message contents, instead, it will only print the commit - subject line. This comes to meet the high demand of better formatted changelogs and requests for - subject line only. Originally, it was a decision to not hide commit subjects that were included in - the commit body via the ``git merge --squash`` command and PSR did not have another alternative. - At this point, all the built-in parsers have the ability to parse squashed commits and separate - them out into their own entry on the changelog. Therefore, the default template no longer needs to - write out the full commit body. See the commit parser options if you want to enable/disable - parsing squash commits. - -* **changelog-rst**: The default ReStructured changelog template will no longer print out the entire - commit message contents, instead, it will only print the commit subject line. This comes to meet - the high demand of better formatted changelogs and requests for subject line only. Originally, it - was a decision to not hide commit subjects that were included in the commit body via the ``git - merge --squash`` command and PSR did not have another alternative. At this point, all the built-in - parsers have the ability to parse squashed commits and separate them out into their own entry on - the changelog. Therefore, the default template no longer needs to write out the full commit body. - See the commit parser options if you want to enable/disable parsing squash commits. - -* **config**: This release switches the ``allow_zero_version`` default to ``false``. This change is - to encourage less ``0.x`` releases as the default but rather allow the experienced developer to - choose when ``0.x`` is appropriate. There are way too many projects in the ecosystems that never - leave ``0.x`` and that is problematic for the industry tools that help auto-update based on - SemVer. We should strive for publishing usable tools and maintaining good forethought for when - compatibility must break. If your configuration already sets the ``allow_zero_version`` value, - this change will have no effect on your project. If you want to use ``0.x`` versions, from the - start then change ``allow_zero_version`` to ``true`` in your configuration. - -* **config**: This release switches the ``changelog.default_templates.mask_initial_release`` default - to ``true``. This change is intended to toggle better recommended outputs of the default - changelog. Conceptually, the very first release is hard to describe--one can only provide new - features as nothing exists yet for the end user. No changelog should be written as there is no - start point to compare the "changes" to. The recommendation instead is to only list a simple - message as ``Initial Release``. This is now the default for PSR when providing the very first - release (no pre-existing tags) in the changelog and release notes. If your configuration already - sets the ``changelog.default_templates.mask_initial_release`` value, then this change will have no - effect on your project. If you do NOT want to mask the first release information, then set - ``changelog.default_templates.mask_initial_release`` to ``false`` in your configuration. - -* **config**: This release switches the ``changelog.mode`` default to ``update``. In this mode, if a - changelog exists, PSR will update the changelog **IF AND ONLY IF** the configured insertion flag - exists in the changelog. The Changelog output will remain unchanged if no insertion flag exists. - The insertion flag may be configured with the ``changelog.insertion_flag`` setting. When upgrading - to ``v10``, you must add the insertion flag manually or you can just delete the changelog file and - run PSR's changelog generation and it will rebuild the changelog (similar to init mode) but it - will add the insertion flag. If your configuration already sets the ``changelog.mode`` value, then - this change will have no effect on your project. If you would rather the changelog be generated - from scratch every release, than set the ``changelog.mode`` value to ``init`` in your - configuration. - -* **github-action**: The ``root_options`` action input parameter has been removed because it created - a command injection vulernability for arbitrary code to execute within the container context of - the GitHub action if a command injection code was provided as part of the ``root_options`` - parameter string. To eliminate the vulnerability, each relevant option that can be provided to - ``semantic-release`` has been individually added as its own parameter and will be processed - individually to prevent command injection. Please review our `Github Actions Configuration`__ page - on the Python Semantic Release Documentation website to review the newly available configuration - options that replace the ``root_options`` parameter. - -* **parser-conventional**: Any breaking change footer messages that the conventional commit parser - detects will now be removed from the ``commit.descriptions[]`` list but maintained in and only in - the ``commit.breaking_descriptions[]`` list. Previously, the descriptions included all text from - the commit message but that was redundant as the default changelog now handles breaking change - footers in its own section. - -* **parser-conventional**: Any issue resolution footers that the parser detects will now be removed - from the ``commit.descriptions[]`` list. Previously, the descriptions included all text from the - commit message but now that the parser pulls out the issue numbers the numbers will be included in - the ``commit.linked_issues`` tuple for user extraction in any changelog generation. - -* **parser-conventional**: Any release notice footer messages that the commit parser detects will - now be removed from the ``commit.descriptions[]`` list but maintained in and only in the - ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message - but that was redundant as the default changelog now handles release notice footers in its own - section. - -* **parser-conventional**: Generally, a pull request or merge request number reference is included - in the subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks - for this reference and extracts it into the ``commit.linked_merge_request`` and the - ``commit.linked_pull_request`` attributes of a commit object. Since this is now pulled out - individually, it is cleaner to remove this from the first line of the ``commit.descriptions`` list - (ie. the subject line) so that changelog macros do not have to replace the text but instead only - append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator - (`#` or ``!``). - -* **parser-conventional**: The configuration setting ``commit_parser_options.ignore_merge_commits`` - is now set to ``true`` by default. The feature to ignore squash commits was introduced in - ``v9.18.0`` and was originally set to ``false`` to prevent unexpected results on a non-breaking - update. The ignore merge commits feature prevents additional unnecessary processing on a commit - message that likely will not match a commit message syntax. Most merge commits are syntactically - pre-defined by Git or Remote Version Control System (ex. GitHub, etc.) and do not follow a commit - convention (nor should they). The larger issue with merge commits is that they ultimately are a - full copy of all the changes that were previously created and committed. The merge commit itself - ensures that the previous commit tree is maintained in history, therefore the commit message - always exists. If merge commits are parsed, it generally creates duplicate messages that will end - up in your changelog, which is less than desired in most cases. If you have previously used the - ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will - want this setting set to ``true`` to improve parsing speed. You can also now remove the merge - commit exclude pattern from the list as well to improve parsing speed. If this functionality is - not desired, you will need to update your configuration to change the new setting to ``false``. - -* **parser-conventional**: The configuration setting ``commit_parser_options.parse_squash_commits`` - is now set to ``true`` by default. The feature to parse squash commits was introduced in - ``v9.17.0`` and was originally set to ``false`` to prevent unexpected results on a non-breaking - update. The parse squash commits feature attempts to find additional commits of the same commit - type within the body of a single commit message. When squash commits are found, Python Semantic - Release will separate out each commit into its own artificial commit object and parse them - individually. This potentially can change the resulting version bump if a larger bump was detected - within the squashed components. It also allows for the changelog and release notes to separately - order and display each commit as originally written. If this is not desired, you will need to - update your configuration to change the new setting to ``false``. - -* **parser-emoji**: Any issue resolution footers that the parser detects will now be removed from - the ``commit.descriptions[]`` list. Previously, the descriptions included all text from the commit - message but now that the parser pulls out the issue numbers the numbers will be included in the - ``commit.linked_issues`` tuple for user extraction in any changelog generation. - -* **parser-emoji**: Any release notice footer messages that the emoji commit parser detects will now - be removed from the ``commit.descriptions[]`` list but maintained in and only in the - ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message - but that was redundant as the default changelog now handles release notice footers in its own - section. - -* **parser-emoji**: Generally, a pull request or merge request number reference is included in the - subject line at the end within parentheses on some common VCS's (e.g. GitHub). PSR now looks for - these references and extract it into the ``commit.linked_merge_request`` field of a commit object. - Since this is now pulled out individually, it is cleaner to remove this from the first line of the - ``commit.descriptions`` list (ie. the subject line) so that changelog macros do not have to - replace the text but instead only append a PR/MR link to the end of the line. The reference will - maintain the PR/MR prefix indicator (e.g. ``#`` or ``!``). - -* **parser-emoji**: The configuration setting ``commit_parser_options.ignore_merge_commits`` is now - set to ``true`` by default. The feature to ignore squash commits was introduced in ``v9.18.0`` and - was originally set to ``false`` to prevent unexpected results on a non-breaking update. The ignore - merge commits feature prevents additional unnecessary processing on a commit message that likely - will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or - Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should - they). The larger issue with merge commits is that they ultimately are a full copy of all the - changes that were previously created and committed. The merge commit itself ensures that the - previous commit tree is maintained in history, therefore the commit message always exists. If - merge commits are parsed, it generally creates duplicate messages that will end up in your - changelog, which is less than desired in most cases. If you have previously used the - ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will - want this setting set to ``true`` to improve parsing speed. You can also now remove the merge - commit exclude pattern from the list as well to improve parsing speed. If this functionality is - not desired, you will need to update your configuration to change the new setting to ``false``. - -* **parser-emoji**: The configuration setting ``commit_parser_options.parse_squash_commits`` is now - set to ``true`` by default. The feature to parse squash commits was introduced in ``v9.17.0`` and - was originally set to ``false`` to prevent unexpected results on a non-breaking update. The parse - squash commits feature attempts to find additional commits of the same commit type within the body - of a single commit message. When squash commits are found, Python Semantic Release will separate - out each commit into its own artificial commit object and parse them individually. This - potentially can change the resulting version bump if a larger bump was detected within the - squashed components. It also allows for the changelog and release notes to separately order and - display each commit as originally written. If this is not desired, you will need to update your - configuration to change the new setting to ``false``. - -* **parser-scipy**: Any issue resolution footers that the parser detects will now be removed from - the commit.descriptions[] list. Previously, the descriptions included all text from the commit - message but now that the parser pulls out the issue numbers the numbers will be included in the - commit.linked_issues tuple for user extraction in any changelog generation. - -* **parser-scipy**: Any release notice footer messages that the commit parser detects will now be - removed from the ``commit.descriptions[]`` list but maintained in and only in the - ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message - but that was redundant as the default changelog now handles release notice footers in its own - section. - -* **parser-scipy**: Generally, a pull request or merge request number reference is included in the - subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks for - this reference and extracts it into the ``commit.linked_merge_request`` and the - ``commit.linked_pull_request`` attributes of a commit object. Since this is now pulled out - individually, it is cleaner to remove this from the first line of the ``commit.descriptions`` list - (ie. the subject line) so that changelog macros do not have to replace the text but instead only - append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator - (`#` or ``!``). - -* **parser-scipy**: The configuration setting ``commit_parser_options.ignore_merge_commits`` is now - set to ``true`` by default. The feature to ignore squash commits was introduced in ``v9.18.0`` and - was originally set to ``false`` to prevent unexpected results on a non-breaking update. The ignore - merge commits feature prevents additional unnecessary processing on a commit message that likely - will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or - Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should - they). The larger issue with merge commits is that they ultimately are a full copy of all the - changes that were previously created and committed. The merge commit itself ensures that the - previous commit tree is maintained in history, therefore the commit message always exists. If - merge commits are parsed, it generally creates duplicate messages that will end up in your - changelog, which is less than desired in most cases. If you have previously used the - ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will - want this setting set to ``true`` to improve parsing speed. You can also now remove the merge - commit exclude pattern from the list as well to improve parsing speed. If this functionality is - not desired, you will need to update your configuration to change the new setting to ``false``. - -* **parser-scipy**: The configuration setting ``commit_parser_options.parse_squash_commits`` is now - set to ``true`` by default. The feature to parse squash commits was introduced in ``v9.17.0`` and - was originally set to ``false`` to prevent unexpected results on a non-breaking update. The parse - squash commits feature attempts to find additional commits of the same commit type within the body - of a single commit message. When squash commits are found, Python Semantic Release will separate - out each commit into its own artificial commit object and parse them individually. This - potentially can change the resulting version bump if a larger bump was detected within the - squashed components. It also allows for the changelog and release notes to separately order and - display each commit as originally written. If this is not desired, you will need to update your - configuration to change the new setting to ``false``. - -.. _#733: https://github.com/python-semantic-release/python-semantic-release/issues/733 -.. _080e4bc: https://github.com/python-semantic-release/python-semantic-release/commit/080e4bcb14048a2dd10445546a7ee3159b3ab85c -.. _0bed906: https://github.com/python-semantic-release/python-semantic-release/commit/0bed9069df67ae806ad0a15f8434ac4efcc6ba31 -.. _0e114c3: https://github.com/python-semantic-release/python-semantic-release/commit/0e114c3458a24b87bfd2d6b0cd3f5cfdc9497084 -.. _16465f1: https://github.com/python-semantic-release/python-semantic-release/commit/16465f133386b09627d311727a6f8d24dd8f174f -.. _203d29d: https://github.com/python-semantic-release/python-semantic-release/commit/203d29d9d6b8e862eabe2f99dbd27eabf04e75e2 -.. _3cfee76: https://github.com/python-semantic-release/python-semantic-release/commit/3cfee76032662bda6fbdd7e2585193213e4f9da2 -.. _4aa6a6e: https://github.com/python-semantic-release/python-semantic-release/commit/4aa6a6edbff75889e09f32f7cba52cb90c9fb626 -.. _4e52f4b: https://github.com/python-semantic-release/python-semantic-release/commit/4e52f4bba46e96a4762f97d306f15ae52c5cea1b -.. _4ea92ec: https://github.com/python-semantic-release/python-semantic-release/commit/4ea92ec34dcd45d8cbab24e38e55289617b2d728 -.. _514a922: https://github.com/python-semantic-release/python-semantic-release/commit/514a922fa87721e2500062dcae841bedd84dc1fe -.. _5310d0c: https://github.com/python-semantic-release/python-semantic-release/commit/5310d0c700840538f27874394b9964bf09cd69b1 -.. _58308e3: https://github.com/python-semantic-release/python-semantic-release/commit/58308e31bb6306aac3a985af01eb779dc923d3f0 -.. _59bf084: https://github.com/python-semantic-release/python-semantic-release/commit/59bf08440a15269afaac81d78dd03ee418f9fd6b -.. _5fb02ab: https://github.com/python-semantic-release/python-semantic-release/commit/5fb02ab6e3b8278ecbf92ed35083ffb595bc19b8 -.. _634fffe: https://github.com/python-semantic-release/python-semantic-release/commit/634fffea29157e9b6305b21802c78ac245454265 -.. _67b2ae0: https://github.com/python-semantic-release/python-semantic-release/commit/67b2ae0050cce540a4126fe280cca6dc4bcf5d3f -.. _6aad7f1: https://github.com/python-semantic-release/python-semantic-release/commit/6aad7f17e64fb4717ddd7a9e94d2a730be6a3bd9 -.. _6cd0fbe: https://github.com/python-semantic-release/python-semantic-release/commit/6cd0fbeb44e16d394c210216c7099afa51f5a4a3 -.. _6fcdc99: https://github.com/python-semantic-release/python-semantic-release/commit/6fcdc99e9462b1186ea9488fc14e4e18f8c7fdb3 -.. _731466f: https://github.com/python-semantic-release/python-semantic-release/commit/731466fec4e06fe71f6c4addd4ae2ec2182ae9c1 -.. _7d39e76: https://github.com/python-semantic-release/python-semantic-release/commit/7d39e7675f859463b54751d59957b869d5d8395c -.. _7e8dc13: https://github.com/python-semantic-release/python-semantic-release/commit/7e8dc13c0b048a95d01f7aecfbe4eeedcddec9a4 -.. _8a51525: https://github.com/python-semantic-release/python-semantic-release/commit/8a5152573b9175f01be06d0c4531ea0ca4de8dd4 -.. _8bed5bc: https://github.com/python-semantic-release/python-semantic-release/commit/8bed5bcca4a5759af0e3fb24eadf14aa4e4f53c9 -.. _a082896: https://github.com/python-semantic-release/python-semantic-release/commit/a08289693085153effdafe3c6ff235a1777bb1fa -.. _a5f5e04: https://github.com/python-semantic-release/python-semantic-release/commit/a5f5e042ae9af909ee9e3ddf57c78adbc92ce378 -.. _b1bb0e5: https://github.com/python-semantic-release/python-semantic-release/commit/b1bb0e55910715754eebef6cb5b21ebed5ee8d68 -.. _b271cbb: https://github.com/python-semantic-release/python-semantic-release/commit/b271cbb2d3e8b86d07d1358b2e7424ccff6ae186 -.. _b6307cb: https://github.com/python-semantic-release/python-semantic-release/commit/b6307cb649043bbcc7ad9f15ac5ac6728914f443 -.. _b757603: https://github.com/python-semantic-release/python-semantic-release/commit/b757603e77ebe26d8a14758d78fd21163a9059b2 -.. _bd3e7bf: https://github.com/python-semantic-release/python-semantic-release/commit/bd3e7bfa86d53a03f03ac419399847712c523b02 -.. _c4d45ec: https://github.com/python-semantic-release/python-semantic-release/commit/c4d45ec46dfa81f645c25ea18ffffe9635922603 -.. _c6b6eab: https://github.com/python-semantic-release/python-semantic-release/commit/c6b6eabbfe100d2c741620eb3fa12a382531fa94 -.. _d4f128e: https://github.com/python-semantic-release/python-semantic-release/commit/d4f128e75e33256c0163fbb475c7c41e18f65147 -.. _da4140f: https://github.com/python-semantic-release/python-semantic-release/commit/da4140f3e3a2ed03c05064f35561b4584f517105 -.. _e7ac155: https://github.com/python-semantic-release/python-semantic-release/commit/e7ac155a91fc2e735d3cbf9b66fb4e5ff40a1466 -.. _eed63fa: https://github.com/python-semantic-release/python-semantic-release/commit/eed63fa9f6e762f55700fc85ef3ebdc0d3144f21 -.. _fb3da27: https://github.com/python-semantic-release/python-semantic-release/commit/fb3da27650ff15bcdb3b7badc919bd8a9a73238d -.. _PR#1244: https://github.com/python-semantic-release/python-semantic-release/pull/1244 -.. _PR#1245: https://github.com/python-semantic-release/python-semantic-release/pull/1245 - - .. _changelog-v9.21.1: v9.21.1 (2025-05-05) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index 6e90dfc25..e080d3415 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -371,8 +371,8 @@ to the remote repository. This option is equivalent to adding either ``--push`` """""""""""""""" .. important:: - This option has been removed in version 10.0.0 and newer because of a - command injection vulnerability. Please update as to v10.0.0 as soon + This option has been removed in $NEW_RELEASE_TAG and newer because of a + command injection vulnerability. Please update as to $NEW_RELEASE_TAG as soon as possible. Additional options for the main ``semantic-release`` command, which will come @@ -382,7 +382,7 @@ before the :ref:`version ` subcommand. .. code:: yaml - - uses: python-semantic-release/python-semantic-release@v10.0.0 + - uses: python-semantic-release/python-semantic-release@v9.21.1 with: root_options: "-vv --noop" @@ -688,8 +688,8 @@ This is useful for testing the action without actually publishing anything. """""""""""""""" .. important:: - This option has been removed in version 10.0.0 and newer because of a - command injection vulnerability. Please update as to v10.0.0 as soon + This option has been removed in $NEW_RELEASE_TAG and newer because of a + command injection vulnerability. Please update as to $NEW_RELEASE_TAG as soon as possible. Additional options for the main ``semantic-release`` command, which will come @@ -699,7 +699,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v10.0.0 + - uses: python-semantic-release/publish-action@v9.21.1 with: root_options: "-vv --noop" @@ -873,14 +873,14 @@ to the GitHub Release Assets as well. - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v10.0.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.0 + uses: python-semantic-release/publish-action@v9.21.1 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -979,7 +979,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@v10.0.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -1038,14 +1038,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v10.0.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: directory: ${{ env.SUBMODULE_1_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release submodule 2 id: release-submod-2 - uses: python-semantic-release/python-semantic-release@v10.0.0 + uses: python-semantic-release/python-semantic-release@v9.21.1 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1057,7 +1057,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.0 + uses: python-semantic-release/publish-action@v9.21.1 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -1065,7 +1065,7 @@ Publish Action. tag: ${{ steps.release-submod-1.outputs.tag }} - name: Publish | Upload package 2 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.0 + uses: python-semantic-release/publish-action@v9.21.1 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/docs/configuration/configuration.rst b/docs/configuration/configuration.rst index 89bfc7862..4dc0f2e14 100644 --- a/docs/configuration/configuration.rst +++ b/docs/configuration/configuration.rst @@ -142,7 +142,7 @@ version to be ``1.0.0``, regardless of patch, minor, or major change level. Additionally, when ``allow_zero_version`` is set to ``false``, the :ref:`config-major_on_zero` setting is ignored. -*Default changed to ``false`` in 10.0.0* +*Default changed to ``false`` in $NEW_RELEASE_TAG* **Default:** ``false`` @@ -390,7 +390,7 @@ is there to document? The message details can be found in the ``first_release.md.j2`` and ``first_release.rst.j2`` templates of the default changelog template directory. -*Default changed to ``true`` in 10.0.0.* +*Default changed to ``true`` in $NEW_RELEASE_TAG.* **Default:** ``true`` @@ -664,7 +664,7 @@ The patterns in this list are treated as regular expressions. ``mode`` ******** -*Introduced in v9.10.0. Default changed to `update` in 10.0.0.* +*Introduced in v9.10.0. Default changed to `update` in $NEW_RELEASE_TAG.* **Type:** ``Literal["init", "update"]`` @@ -1351,7 +1351,7 @@ The regular expression generated from the ``version_variables`` definition will: 2. The variable name defined by ``variable`` and the version must be separated by an operand symbol (``=``, ``:``, ``:=``, or ``@``). Whitespace is optional around - the symbol. As of 10.0.0, a double-equals (``==``) operator is also supported + the symbol. As of $NEW_RELEASE_TAG, a double-equals (``==``) operator is also supported as a valid operand symbol. 3. The value of the variable must match a `SemVer`_ regular expression and can be diff --git a/pyproject.toml b/pyproject.toml index ec0bc1f8e..addaf110e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "10.0.0" +version = "9.21.1" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } From 092ace20f4ebed6a656da54b499076f1a5b803c8 Mon Sep 17 00:00:00 2001 From: semantic-release Date: Sun, 25 May 2025 07:53:38 +0000 Subject: [PATCH 097/120] 10.0.0 Automatically generated by python-semantic-release --- CHANGELOG.rst | 357 ++++++++++++++++++ .../automatic-releases/github-actions.rst | 26 +- docs/configuration/configuration.rst | 8 +- pyproject.toml | 2 +- 4 files changed, 375 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5714ef3eb..61fa77784 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,363 @@ CHANGELOG ========= +.. _changelog-v10.0.0: + +v10.0.0 (2025-05-25) +==================== + +✨ Features +----------- + +* **cmd-version**: Enable ``version_variables`` version stamp of vars with double-equals + (`PR#1244`_, `080e4bc`_) + +* **parser-conventional**: Set parser to evaluate all squashed commits by default (`6fcdc99`_) + +* **parser-conventional**: Set parser to ignore merge commits by default (`59bf084`_) + +* **parser-emoji**: Set parser to evaluate all squashed commits by default (`514a922`_) + +* **parser-emoji**: Set parser to ignore merge commits by default (`8a51525`_) + +* **parser-scipy**: Set parser to evaluate all squashed commits by default (`634fffe`_) + +* **parser-scipy**: Set parser to ignore merge commits by default (`d4f128e`_) + +πŸͺ² Bug Fixes +------------ + +* **changelog-md**: Change to 1-line descriptions in markdown template, closes `#733`_ (`e7ac155`_) + +* **changelog-rst**: Change to 1-line descriptions in the default ReStructuredText template, closes + `#733`_ (`731466f`_) + +* **cli**: Adjust verbosity parameter to enable silly-level logging (`bd3e7bf`_) + +* **github-action**: Resolve command injection vulnerability in action script (`fb3da27`_) + +* **parser-conventional**: Remove breaking change footer messages from commit descriptions + (`b271cbb`_) + +* **parser-conventional**: Remove issue footer messages from commit descriptions (`b1bb0e5`_) + +* **parser-conventional**: Remove PR/MR references from commit subject line (`eed63fa`_) + +* **parser-conventional**: Remove release notice footer messages from commit descriptions + (`7e8dc13`_) + +* **parser-emoji**: Remove issue footer messages from commit descriptions (`b757603`_) + +* **parser-emoji**: Remove PR/MR references from commit subject line (`16465f1`_) + +* **parser-emoji**: Remove release notice footer messages from commit descriptions (`b6307cb`_) + +* **parser-scipy**: Remove issue footer messages from commit descriptions (`3cfee76`_) + +* **parser-scipy**: Remove PR/MR references from commit subject line (`da4140f`_) + +* **parser-scipy**: Remove release notice footer messages from commit descriptions (`58308e3`_) + +πŸ“– Documentation +---------------- + +* Refactor documentation page navigation (`4e52f4b`_) + +* **algorithm**: Remove out-of-date algorithm description (`6cd0fbe`_) + +* **commit-parsing**: Define limitation of revert commits with the scipy parser (`5310d0c`_) + +* **configuration**: Change default value for ``allow_zero_version`` in the description (`203d29d`_) + +* **configuration**: Change the default for the base changelog's ``mask_initial_release`` value + (`5fb02ab`_) + +* **configuration**: Change the default value for ``changelog.mode`` in the setting description + (`0bed906`_) + +* **configuration**: Update ``version_variables`` section to include double-equals operand support + (`PR#1244`_, `080e4bc`_) + +* **contributing**: Refactor contributing & contributors layout (`8bed5bc`_) + +* **github-actions**: Add reference to manual release workflow example (`6aad7f1`_) + +* **github-actions**: Change recommended workflow to separate release from deploy (`67b2ae0`_) + +* **github-actions**: Update ``python-semantic-release/publish-action`` parameter notes (`c4d45ec`_) + +* **github-actions**: Update PSR action parameter documenation (`a082896`_) + +* **upgrading**: Re-locate version upgrade guides into ``Upgrading PSR`` (`a5f5e04`_) + +* **upgrading-v10**: Added migration guide for v9 to v10 (`4ea92ec`_) + +βš™οΈ Build System +---------------- + +* **deps**: Prevent update to ``click@8.2.0`` (`PR#1245`_, `4aa6a6e`_) + +♻️ Refactoring +--------------- + +* **config**: Change ``allow_zero_version`` default to ``false`` (`c6b6eab`_) + +* **config**: Change ``changelog.default_templates.mask_initial_release`` default to ``true`` + (`0e114c3`_) + +* **config**: Change ``changelog.mode`` default to ``update`` (`7d39e76`_) + +πŸ’₯ Breaking Changes +------------------- + +* **changelog-md**: The default Markdown changelog template and release notes template will no + longer print out the entire commit message contents, instead, it will only print the commit + subject line. This comes to meet the high demand of better formatted changelogs and requests for + subject line only. Originally, it was a decision to not hide commit subjects that were included in + the commit body via the ``git merge --squash`` command and PSR did not have another alternative. + At this point, all the built-in parsers have the ability to parse squashed commits and separate + them out into their own entry on the changelog. Therefore, the default template no longer needs to + write out the full commit body. See the commit parser options if you want to enable/disable + parsing squash commits. + +* **changelog-rst**: The default ReStructured changelog template will no longer print out the entire + commit message contents, instead, it will only print the commit subject line. This comes to meet + the high demand of better formatted changelogs and requests for subject line only. Originally, it + was a decision to not hide commit subjects that were included in the commit body via the ``git + merge --squash`` command and PSR did not have another alternative. At this point, all the built-in + parsers have the ability to parse squashed commits and separate them out into their own entry on + the changelog. Therefore, the default template no longer needs to write out the full commit body. + See the commit parser options if you want to enable/disable parsing squash commits. + +* **config**: This release switches the ``allow_zero_version`` default to ``false``. This change is + to encourage less ``0.x`` releases as the default but rather allow the experienced developer to + choose when ``0.x`` is appropriate. There are way too many projects in the ecosystems that never + leave ``0.x`` and that is problematic for the industry tools that help auto-update based on + SemVer. We should strive for publishing usable tools and maintaining good forethought for when + compatibility must break. If your configuration already sets the ``allow_zero_version`` value, + this change will have no effect on your project. If you want to use ``0.x`` versions, from the + start then change ``allow_zero_version`` to ``true`` in your configuration. + +* **config**: This release switches the ``changelog.default_templates.mask_initial_release`` default + to ``true``. This change is intended to toggle better recommended outputs of the default + changelog. Conceptually, the very first release is hard to describe--one can only provide new + features as nothing exists yet for the end user. No changelog should be written as there is no + start point to compare the "changes" to. The recommendation instead is to only list a simple + message as ``Initial Release``. This is now the default for PSR when providing the very first + release (no pre-existing tags) in the changelog and release notes. If your configuration already + sets the ``changelog.default_templates.mask_initial_release`` value, then this change will have no + effect on your project. If you do NOT want to mask the first release information, then set + ``changelog.default_templates.mask_initial_release`` to ``false`` in your configuration. + +* **config**: This release switches the ``changelog.mode`` default to ``update``. In this mode, if a + changelog exists, PSR will update the changelog **IF AND ONLY IF** the configured insertion flag + exists in the changelog. The Changelog output will remain unchanged if no insertion flag exists. + The insertion flag may be configured with the ``changelog.insertion_flag`` setting. When upgrading + to ``v10``, you must add the insertion flag manually or you can just delete the changelog file and + run PSR's changelog generation and it will rebuild the changelog (similar to init mode) but it + will add the insertion flag. If your configuration already sets the ``changelog.mode`` value, then + this change will have no effect on your project. If you would rather the changelog be generated + from scratch every release, than set the ``changelog.mode`` value to ``init`` in your + configuration. + +* **github-action**: The ``root_options`` action input parameter has been removed because it created + a command injection vulernability for arbitrary code to execute within the container context of + the GitHub action if a command injection code was provided as part of the ``root_options`` + parameter string. To eliminate the vulnerability, each relevant option that can be provided to + ``semantic-release`` has been individually added as its own parameter and will be processed + individually to prevent command injection. Please review our `Github Actions Configuration`__ page + on the Python Semantic Release Documentation website to review the newly available configuration + options that replace the ``root_options`` parameter. + +* **parser-conventional**: Any breaking change footer messages that the conventional commit parser + detects will now be removed from the ``commit.descriptions[]`` list but maintained in and only in + the ``commit.breaking_descriptions[]`` list. Previously, the descriptions included all text from + the commit message but that was redundant as the default changelog now handles breaking change + footers in its own section. + +* **parser-conventional**: Any issue resolution footers that the parser detects will now be removed + from the ``commit.descriptions[]`` list. Previously, the descriptions included all text from the + commit message but now that the parser pulls out the issue numbers the numbers will be included in + the ``commit.linked_issues`` tuple for user extraction in any changelog generation. + +* **parser-conventional**: Any release notice footer messages that the commit parser detects will + now be removed from the ``commit.descriptions[]`` list but maintained in and only in the + ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message + but that was redundant as the default changelog now handles release notice footers in its own + section. + +* **parser-conventional**: Generally, a pull request or merge request number reference is included + in the subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks + for this reference and extracts it into the ``commit.linked_merge_request`` and the + ``commit.linked_pull_request`` attributes of a commit object. Since this is now pulled out + individually, it is cleaner to remove this from the first line of the ``commit.descriptions`` list + (ie. the subject line) so that changelog macros do not have to replace the text but instead only + append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator + (`#` or ``!``). + +* **parser-conventional**: The configuration setting ``commit_parser_options.ignore_merge_commits`` + is now set to ``true`` by default. The feature to ignore squash commits was introduced in + ``v9.18.0`` and was originally set to ``false`` to prevent unexpected results on a non-breaking + update. The ignore merge commits feature prevents additional unnecessary processing on a commit + message that likely will not match a commit message syntax. Most merge commits are syntactically + pre-defined by Git or Remote Version Control System (ex. GitHub, etc.) and do not follow a commit + convention (nor should they). The larger issue with merge commits is that they ultimately are a + full copy of all the changes that were previously created and committed. The merge commit itself + ensures that the previous commit tree is maintained in history, therefore the commit message + always exists. If merge commits are parsed, it generally creates duplicate messages that will end + up in your changelog, which is less than desired in most cases. If you have previously used the + ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will + want this setting set to ``true`` to improve parsing speed. You can also now remove the merge + commit exclude pattern from the list as well to improve parsing speed. If this functionality is + not desired, you will need to update your configuration to change the new setting to ``false``. + +* **parser-conventional**: The configuration setting ``commit_parser_options.parse_squash_commits`` + is now set to ``true`` by default. The feature to parse squash commits was introduced in + ``v9.17.0`` and was originally set to ``false`` to prevent unexpected results on a non-breaking + update. The parse squash commits feature attempts to find additional commits of the same commit + type within the body of a single commit message. When squash commits are found, Python Semantic + Release will separate out each commit into its own artificial commit object and parse them + individually. This potentially can change the resulting version bump if a larger bump was detected + within the squashed components. It also allows for the changelog and release notes to separately + order and display each commit as originally written. If this is not desired, you will need to + update your configuration to change the new setting to ``false``. + +* **parser-emoji**: Any issue resolution footers that the parser detects will now be removed from + the ``commit.descriptions[]`` list. Previously, the descriptions included all text from the commit + message but now that the parser pulls out the issue numbers the numbers will be included in the + ``commit.linked_issues`` tuple for user extraction in any changelog generation. + +* **parser-emoji**: Any release notice footer messages that the emoji commit parser detects will now + be removed from the ``commit.descriptions[]`` list but maintained in and only in the + ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message + but that was redundant as the default changelog now handles release notice footers in its own + section. + +* **parser-emoji**: Generally, a pull request or merge request number reference is included in the + subject line at the end within parentheses on some common VCS's (e.g. GitHub). PSR now looks for + these references and extract it into the ``commit.linked_merge_request`` field of a commit object. + Since this is now pulled out individually, it is cleaner to remove this from the first line of the + ``commit.descriptions`` list (ie. the subject line) so that changelog macros do not have to + replace the text but instead only append a PR/MR link to the end of the line. The reference will + maintain the PR/MR prefix indicator (e.g. ``#`` or ``!``). + +* **parser-emoji**: The configuration setting ``commit_parser_options.ignore_merge_commits`` is now + set to ``true`` by default. The feature to ignore squash commits was introduced in ``v9.18.0`` and + was originally set to ``false`` to prevent unexpected results on a non-breaking update. The ignore + merge commits feature prevents additional unnecessary processing on a commit message that likely + will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or + Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should + they). The larger issue with merge commits is that they ultimately are a full copy of all the + changes that were previously created and committed. The merge commit itself ensures that the + previous commit tree is maintained in history, therefore the commit message always exists. If + merge commits are parsed, it generally creates duplicate messages that will end up in your + changelog, which is less than desired in most cases. If you have previously used the + ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will + want this setting set to ``true`` to improve parsing speed. You can also now remove the merge + commit exclude pattern from the list as well to improve parsing speed. If this functionality is + not desired, you will need to update your configuration to change the new setting to ``false``. + +* **parser-emoji**: The configuration setting ``commit_parser_options.parse_squash_commits`` is now + set to ``true`` by default. The feature to parse squash commits was introduced in ``v9.17.0`` and + was originally set to ``false`` to prevent unexpected results on a non-breaking update. The parse + squash commits feature attempts to find additional commits of the same commit type within the body + of a single commit message. When squash commits are found, Python Semantic Release will separate + out each commit into its own artificial commit object and parse them individually. This + potentially can change the resulting version bump if a larger bump was detected within the + squashed components. It also allows for the changelog and release notes to separately order and + display each commit as originally written. If this is not desired, you will need to update your + configuration to change the new setting to ``false``. + +* **parser-scipy**: Any issue resolution footers that the parser detects will now be removed from + the commit.descriptions[] list. Previously, the descriptions included all text from the commit + message but now that the parser pulls out the issue numbers the numbers will be included in the + commit.linked_issues tuple for user extraction in any changelog generation. + +* **parser-scipy**: Any release notice footer messages that the commit parser detects will now be + removed from the ``commit.descriptions[]`` list but maintained in and only in the + ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message + but that was redundant as the default changelog now handles release notice footers in its own + section. + +* **parser-scipy**: Generally, a pull request or merge request number reference is included in the + subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks for + this reference and extracts it into the ``commit.linked_merge_request`` and the + ``commit.linked_pull_request`` attributes of a commit object. Since this is now pulled out + individually, it is cleaner to remove this from the first line of the ``commit.descriptions`` list + (ie. the subject line) so that changelog macros do not have to replace the text but instead only + append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator + (`#` or ``!``). + +* **parser-scipy**: The configuration setting ``commit_parser_options.ignore_merge_commits`` is now + set to ``true`` by default. The feature to ignore squash commits was introduced in ``v9.18.0`` and + was originally set to ``false`` to prevent unexpected results on a non-breaking update. The ignore + merge commits feature prevents additional unnecessary processing on a commit message that likely + will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or + Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should + they). The larger issue with merge commits is that they ultimately are a full copy of all the + changes that were previously created and committed. The merge commit itself ensures that the + previous commit tree is maintained in history, therefore the commit message always exists. If + merge commits are parsed, it generally creates duplicate messages that will end up in your + changelog, which is less than desired in most cases. If you have previously used the + ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will + want this setting set to ``true`` to improve parsing speed. You can also now remove the merge + commit exclude pattern from the list as well to improve parsing speed. If this functionality is + not desired, you will need to update your configuration to change the new setting to ``false``. + +* **parser-scipy**: The configuration setting ``commit_parser_options.parse_squash_commits`` is now + set to ``true`` by default. The feature to parse squash commits was introduced in ``v9.17.0`` and + was originally set to ``false`` to prevent unexpected results on a non-breaking update. The parse + squash commits feature attempts to find additional commits of the same commit type within the body + of a single commit message. When squash commits are found, Python Semantic Release will separate + out each commit into its own artificial commit object and parse them individually. This + potentially can change the resulting version bump if a larger bump was detected within the + squashed components. It also allows for the changelog and release notes to separately order and + display each commit as originally written. If this is not desired, you will need to update your + configuration to change the new setting to ``false``. + +.. _#733: https://github.com/python-semantic-release/python-semantic-release/issues/733 +.. _080e4bc: https://github.com/python-semantic-release/python-semantic-release/commit/080e4bcb14048a2dd10445546a7ee3159b3ab85c +.. _0bed906: https://github.com/python-semantic-release/python-semantic-release/commit/0bed9069df67ae806ad0a15f8434ac4efcc6ba31 +.. _0e114c3: https://github.com/python-semantic-release/python-semantic-release/commit/0e114c3458a24b87bfd2d6b0cd3f5cfdc9497084 +.. _16465f1: https://github.com/python-semantic-release/python-semantic-release/commit/16465f133386b09627d311727a6f8d24dd8f174f +.. _203d29d: https://github.com/python-semantic-release/python-semantic-release/commit/203d29d9d6b8e862eabe2f99dbd27eabf04e75e2 +.. _3cfee76: https://github.com/python-semantic-release/python-semantic-release/commit/3cfee76032662bda6fbdd7e2585193213e4f9da2 +.. _4aa6a6e: https://github.com/python-semantic-release/python-semantic-release/commit/4aa6a6edbff75889e09f32f7cba52cb90c9fb626 +.. _4e52f4b: https://github.com/python-semantic-release/python-semantic-release/commit/4e52f4bba46e96a4762f97d306f15ae52c5cea1b +.. _4ea92ec: https://github.com/python-semantic-release/python-semantic-release/commit/4ea92ec34dcd45d8cbab24e38e55289617b2d728 +.. _514a922: https://github.com/python-semantic-release/python-semantic-release/commit/514a922fa87721e2500062dcae841bedd84dc1fe +.. _5310d0c: https://github.com/python-semantic-release/python-semantic-release/commit/5310d0c700840538f27874394b9964bf09cd69b1 +.. _58308e3: https://github.com/python-semantic-release/python-semantic-release/commit/58308e31bb6306aac3a985af01eb779dc923d3f0 +.. _59bf084: https://github.com/python-semantic-release/python-semantic-release/commit/59bf08440a15269afaac81d78dd03ee418f9fd6b +.. _5fb02ab: https://github.com/python-semantic-release/python-semantic-release/commit/5fb02ab6e3b8278ecbf92ed35083ffb595bc19b8 +.. _634fffe: https://github.com/python-semantic-release/python-semantic-release/commit/634fffea29157e9b6305b21802c78ac245454265 +.. _67b2ae0: https://github.com/python-semantic-release/python-semantic-release/commit/67b2ae0050cce540a4126fe280cca6dc4bcf5d3f +.. _6aad7f1: https://github.com/python-semantic-release/python-semantic-release/commit/6aad7f17e64fb4717ddd7a9e94d2a730be6a3bd9 +.. _6cd0fbe: https://github.com/python-semantic-release/python-semantic-release/commit/6cd0fbeb44e16d394c210216c7099afa51f5a4a3 +.. _6fcdc99: https://github.com/python-semantic-release/python-semantic-release/commit/6fcdc99e9462b1186ea9488fc14e4e18f8c7fdb3 +.. _731466f: https://github.com/python-semantic-release/python-semantic-release/commit/731466fec4e06fe71f6c4addd4ae2ec2182ae9c1 +.. _7d39e76: https://github.com/python-semantic-release/python-semantic-release/commit/7d39e7675f859463b54751d59957b869d5d8395c +.. _7e8dc13: https://github.com/python-semantic-release/python-semantic-release/commit/7e8dc13c0b048a95d01f7aecfbe4eeedcddec9a4 +.. _8a51525: https://github.com/python-semantic-release/python-semantic-release/commit/8a5152573b9175f01be06d0c4531ea0ca4de8dd4 +.. _8bed5bc: https://github.com/python-semantic-release/python-semantic-release/commit/8bed5bcca4a5759af0e3fb24eadf14aa4e4f53c9 +.. _a082896: https://github.com/python-semantic-release/python-semantic-release/commit/a08289693085153effdafe3c6ff235a1777bb1fa +.. _a5f5e04: https://github.com/python-semantic-release/python-semantic-release/commit/a5f5e042ae9af909ee9e3ddf57c78adbc92ce378 +.. _b1bb0e5: https://github.com/python-semantic-release/python-semantic-release/commit/b1bb0e55910715754eebef6cb5b21ebed5ee8d68 +.. _b271cbb: https://github.com/python-semantic-release/python-semantic-release/commit/b271cbb2d3e8b86d07d1358b2e7424ccff6ae186 +.. _b6307cb: https://github.com/python-semantic-release/python-semantic-release/commit/b6307cb649043bbcc7ad9f15ac5ac6728914f443 +.. _b757603: https://github.com/python-semantic-release/python-semantic-release/commit/b757603e77ebe26d8a14758d78fd21163a9059b2 +.. _bd3e7bf: https://github.com/python-semantic-release/python-semantic-release/commit/bd3e7bfa86d53a03f03ac419399847712c523b02 +.. _c4d45ec: https://github.com/python-semantic-release/python-semantic-release/commit/c4d45ec46dfa81f645c25ea18ffffe9635922603 +.. _c6b6eab: https://github.com/python-semantic-release/python-semantic-release/commit/c6b6eabbfe100d2c741620eb3fa12a382531fa94 +.. _d4f128e: https://github.com/python-semantic-release/python-semantic-release/commit/d4f128e75e33256c0163fbb475c7c41e18f65147 +.. _da4140f: https://github.com/python-semantic-release/python-semantic-release/commit/da4140f3e3a2ed03c05064f35561b4584f517105 +.. _e7ac155: https://github.com/python-semantic-release/python-semantic-release/commit/e7ac155a91fc2e735d3cbf9b66fb4e5ff40a1466 +.. _eed63fa: https://github.com/python-semantic-release/python-semantic-release/commit/eed63fa9f6e762f55700fc85ef3ebdc0d3144f21 +.. _fb3da27: https://github.com/python-semantic-release/python-semantic-release/commit/fb3da27650ff15bcdb3b7badc919bd8a9a73238d +.. _PR#1244: https://github.com/python-semantic-release/python-semantic-release/pull/1244 +.. _PR#1245: https://github.com/python-semantic-release/python-semantic-release/pull/1245 + + .. _changelog-v9.21.1: v9.21.1 (2025-05-05) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index e080d3415..d8a8bd012 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -371,8 +371,8 @@ to the remote repository. This option is equivalent to adding either ``--push`` """""""""""""""" .. important:: - This option has been removed in $NEW_RELEASE_TAG and newer because of a - command injection vulnerability. Please update as to $NEW_RELEASE_TAG as soon + This option has been removed in v10.0.0 and newer because of a + command injection vulnerability. Please update as to v10.0.0 as soon as possible. Additional options for the main ``semantic-release`` command, which will come @@ -382,7 +382,7 @@ before the :ref:`version ` subcommand. .. code:: yaml - - uses: python-semantic-release/python-semantic-release@v9.21.1 + - uses: python-semantic-release/python-semantic-release@v10.0.0 with: root_options: "-vv --noop" @@ -688,8 +688,8 @@ This is useful for testing the action without actually publishing anything. """""""""""""""" .. important:: - This option has been removed in $NEW_RELEASE_TAG and newer because of a - command injection vulnerability. Please update as to $NEW_RELEASE_TAG as soon + This option has been removed in v10.0.0 and newer because of a + command injection vulnerability. Please update as to v10.0.0 as soon as possible. Additional options for the main ``semantic-release`` command, which will come @@ -699,7 +699,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v9.21.1 + - uses: python-semantic-release/publish-action@v10.0.0 with: root_options: "-vv --noop" @@ -873,14 +873,14 @@ to the GitHub Release Assets as well. - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v9.21.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.1 + uses: python-semantic-release/publish-action@v10.0.0 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -979,7 +979,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.21.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -1038,14 +1038,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v9.21.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: directory: ${{ env.SUBMODULE_1_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release submodule 2 id: release-submod-2 - uses: python-semantic-release/python-semantic-release@v9.21.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1057,7 +1057,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.1 + uses: python-semantic-release/publish-action@v10.0.0 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -1065,7 +1065,7 @@ Publish Action. tag: ${{ steps.release-submod-1.outputs.tag }} - name: Publish | Upload package 2 to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.1 + uses: python-semantic-release/publish-action@v10.0.0 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/docs/configuration/configuration.rst b/docs/configuration/configuration.rst index 4dc0f2e14..08368b337 100644 --- a/docs/configuration/configuration.rst +++ b/docs/configuration/configuration.rst @@ -142,7 +142,7 @@ version to be ``1.0.0``, regardless of patch, minor, or major change level. Additionally, when ``allow_zero_version`` is set to ``false``, the :ref:`config-major_on_zero` setting is ignored. -*Default changed to ``false`` in $NEW_RELEASE_TAG* +*Default changed to ``false`` in v10.0.0* **Default:** ``false`` @@ -390,7 +390,7 @@ is there to document? The message details can be found in the ``first_release.md.j2`` and ``first_release.rst.j2`` templates of the default changelog template directory. -*Default changed to ``true`` in $NEW_RELEASE_TAG.* +*Default changed to ``true`` in v10.0.0.* **Default:** ``true`` @@ -664,7 +664,7 @@ The patterns in this list are treated as regular expressions. ``mode`` ******** -*Introduced in v9.10.0. Default changed to `update` in $NEW_RELEASE_TAG.* +*Introduced in v9.10.0. Default changed to `update` in v10.0.0.* **Type:** ``Literal["init", "update"]`` @@ -1351,7 +1351,7 @@ The regular expression generated from the ``version_variables`` definition will: 2. The variable name defined by ``variable`` and the version must be separated by an operand symbol (``=``, ``:``, ``:=``, or ``@``). Whitespace is optional around - the symbol. As of $NEW_RELEASE_TAG, a double-equals (``==``) operator is also supported + the symbol. As of v10.0.0, a double-equals (``==``) operator is also supported as a valid operand symbol. 3. The value of the variable must match a `SemVer`_ regular expression and can be diff --git a/pyproject.toml b/pyproject.toml index addaf110e..ec0bc1f8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "9.21.1" +version = "10.0.0" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } From e70325e2ad41fb16d33f0dfc179955a3c5d38604 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 03:46:51 -0600 Subject: [PATCH 098/120] chore(changelog): update changelog spelling, links, & shorten breaking change descriptions (#1254) * chore(docs): fix references within docs --- CHANGELOG.rst | 193 +++++------------- docs/api/commands.rst | 2 +- .../automatic-releases/travis.rst | 2 +- 3 files changed, 54 insertions(+), 143 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 61fa77784..ec7decbe1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -89,7 +89,7 @@ v10.0.0 (2025-05-25) * **github-actions**: Update ``python-semantic-release/publish-action`` parameter notes (`c4d45ec`_) -* **github-actions**: Update PSR action parameter documenation (`a082896`_) +* **github-actions**: Update PSR action parameter documentation (`a082896`_) * **upgrading**: Re-locate version upgrade guides into ``Upgrading PSR`` (`a5f5e04`_) @@ -164,13 +164,14 @@ v10.0.0 (2025-05-25) configuration. * **github-action**: The ``root_options`` action input parameter has been removed because it created - a command injection vulernability for arbitrary code to execute within the container context of + a command injection vulnerability for arbitrary code to execute within the container context of the GitHub action if a command injection code was provided as part of the ``root_options`` parameter string. To eliminate the vulnerability, each relevant option that can be provided to ``semantic-release`` has been individually added as its own parameter and will be processed individually to prevent command injection. Please review our `Github Actions Configuration`__ page - on the Python Semantic Release Documentation website to review the newly available configuration - options that replace the ``root_options`` parameter. + to review the newly available configuration options that replace the ``root_options`` parameter. + + __ https://github.com/python-semantic-release/python-semantic-release/blob/v10.0.0/docs/configuration/automatic-releases/github-actions.rst * **parser-conventional**: Any breaking change footer messages that the conventional commit parser detects will now be removed from the ``commit.descriptions[]`` list but maintained in and only in @@ -178,146 +179,56 @@ v10.0.0 (2025-05-25) the commit message but that was redundant as the default changelog now handles breaking change footers in its own section. -* **parser-conventional**: Any issue resolution footers that the parser detects will now be removed - from the ``commit.descriptions[]`` list. Previously, the descriptions included all text from the - commit message but now that the parser pulls out the issue numbers the numbers will be included in - the ``commit.linked_issues`` tuple for user extraction in any changelog generation. - -* **parser-conventional**: Any release notice footer messages that the commit parser detects will - now be removed from the ``commit.descriptions[]`` list but maintained in and only in the - ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message - but that was redundant as the default changelog now handles release notice footers in its own - section. - -* **parser-conventional**: Generally, a pull request or merge request number reference is included - in the subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks - for this reference and extracts it into the ``commit.linked_merge_request`` and the - ``commit.linked_pull_request`` attributes of a commit object. Since this is now pulled out - individually, it is cleaner to remove this from the first line of the ``commit.descriptions`` list - (ie. the subject line) so that changelog macros do not have to replace the text but instead only - append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator - (`#` or ``!``). - -* **parser-conventional**: The configuration setting ``commit_parser_options.ignore_merge_commits`` - is now set to ``true`` by default. The feature to ignore squash commits was introduced in - ``v9.18.0`` and was originally set to ``false`` to prevent unexpected results on a non-breaking - update. The ignore merge commits feature prevents additional unnecessary processing on a commit - message that likely will not match a commit message syntax. Most merge commits are syntactically - pre-defined by Git or Remote Version Control System (ex. GitHub, etc.) and do not follow a commit - convention (nor should they). The larger issue with merge commits is that they ultimately are a - full copy of all the changes that were previously created and committed. The merge commit itself - ensures that the previous commit tree is maintained in history, therefore the commit message - always exists. If merge commits are parsed, it generally creates duplicate messages that will end - up in your changelog, which is less than desired in most cases. If you have previously used the - ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will - want this setting set to ``true`` to improve parsing speed. You can also now remove the merge - commit exclude pattern from the list as well to improve parsing speed. If this functionality is - not desired, you will need to update your configuration to change the new setting to ``false``. - -* **parser-conventional**: The configuration setting ``commit_parser_options.parse_squash_commits`` - is now set to ``true`` by default. The feature to parse squash commits was introduced in - ``v9.17.0`` and was originally set to ``false`` to prevent unexpected results on a non-breaking - update. The parse squash commits feature attempts to find additional commits of the same commit - type within the body of a single commit message. When squash commits are found, Python Semantic - Release will separate out each commit into its own artificial commit object and parse them - individually. This potentially can change the resulting version bump if a larger bump was detected - within the squashed components. It also allows for the changelog and release notes to separately - order and display each commit as originally written. If this is not desired, you will need to - update your configuration to change the new setting to ``false``. - -* **parser-emoji**: Any issue resolution footers that the parser detects will now be removed from - the ``commit.descriptions[]`` list. Previously, the descriptions included all text from the commit - message but now that the parser pulls out the issue numbers the numbers will be included in the - ``commit.linked_issues`` tuple for user extraction in any changelog generation. - -* **parser-emoji**: Any release notice footer messages that the emoji commit parser detects will now - be removed from the ``commit.descriptions[]`` list but maintained in and only in the - ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message - but that was redundant as the default changelog now handles release notice footers in its own - section. - -* **parser-emoji**: Generally, a pull request or merge request number reference is included in the - subject line at the end within parentheses on some common VCS's (e.g. GitHub). PSR now looks for - these references and extract it into the ``commit.linked_merge_request`` field of a commit object. - Since this is now pulled out individually, it is cleaner to remove this from the first line of the - ``commit.descriptions`` list (ie. the subject line) so that changelog macros do not have to - replace the text but instead only append a PR/MR link to the end of the line. The reference will - maintain the PR/MR prefix indicator (e.g. ``#`` or ``!``). - -* **parser-emoji**: The configuration setting ``commit_parser_options.ignore_merge_commits`` is now - set to ``true`` by default. The feature to ignore squash commits was introduced in ``v9.18.0`` and - was originally set to ``false`` to prevent unexpected results on a non-breaking update. The ignore - merge commits feature prevents additional unnecessary processing on a commit message that likely - will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or - Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should - they). The larger issue with merge commits is that they ultimately are a full copy of all the - changes that were previously created and committed. The merge commit itself ensures that the - previous commit tree is maintained in history, therefore the commit message always exists. If - merge commits are parsed, it generally creates duplicate messages that will end up in your - changelog, which is less than desired in most cases. If you have previously used the - ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will - want this setting set to ``true`` to improve parsing speed. You can also now remove the merge - commit exclude pattern from the list as well to improve parsing speed. If this functionality is - not desired, you will need to update your configuration to change the new setting to ``false``. - -* **parser-emoji**: The configuration setting ``commit_parser_options.parse_squash_commits`` is now - set to ``true`` by default. The feature to parse squash commits was introduced in ``v9.17.0`` and - was originally set to ``false`` to prevent unexpected results on a non-breaking update. The parse - squash commits feature attempts to find additional commits of the same commit type within the body - of a single commit message. When squash commits are found, Python Semantic Release will separate - out each commit into its own artificial commit object and parse them individually. This - potentially can change the resulting version bump if a larger bump was detected within the - squashed components. It also allows for the changelog and release notes to separately order and - display each commit as originally written. If this is not desired, you will need to update your - configuration to change the new setting to ``false``. +* **parser-conventional, parser-emoji, parser-scipy**: Any issue resolution footers that the parser + detects will now be removed from the ``commit.descriptions[]`` list. Previously, the descriptions + included all text from the commit message but now that the parser pulls out the issue numbers the + numbers will be included in the ``commit.linked_issues`` tuple for user extraction in any + changelog generation. -* **parser-scipy**: Any issue resolution footers that the parser detects will now be removed from - the commit.descriptions[] list. Previously, the descriptions included all text from the commit - message but now that the parser pulls out the issue numbers the numbers will be included in the - commit.linked_issues tuple for user extraction in any changelog generation. - -* **parser-scipy**: Any release notice footer messages that the commit parser detects will now be - removed from the ``commit.descriptions[]`` list but maintained in and only in the - ``commit.notices[]`` list. Previously, the descriptions included all text from the commit message - but that was redundant as the default changelog now handles release notice footers in its own - section. - -* **parser-scipy**: Generally, a pull request or merge request number reference is included in the - subject line at the end within parentheses on some common VCS's like GitHub. PSR now looks for - this reference and extracts it into the ``commit.linked_merge_request`` and the - ``commit.linked_pull_request`` attributes of a commit object. Since this is now pulled out - individually, it is cleaner to remove this from the first line of the ``commit.descriptions`` list - (ie. the subject line) so that changelog macros do not have to replace the text but instead only - append a PR/MR link to the end of the line. The reference does maintain the PR/MR prefix indicator - (`#` or ``!``). - -* **parser-scipy**: The configuration setting ``commit_parser_options.ignore_merge_commits`` is now - set to ``true`` by default. The feature to ignore squash commits was introduced in ``v9.18.0`` and - was originally set to ``false`` to prevent unexpected results on a non-breaking update. The ignore - merge commits feature prevents additional unnecessary processing on a commit message that likely - will not match a commit message syntax. Most merge commits are syntactically pre-defined by Git or - Remote Version Control System (ex. GitHub, etc.) and do not follow a commit convention (nor should - they). The larger issue with merge commits is that they ultimately are a full copy of all the - changes that were previously created and committed. The merge commit itself ensures that the - previous commit tree is maintained in history, therefore the commit message always exists. If - merge commits are parsed, it generally creates duplicate messages that will end up in your - changelog, which is less than desired in most cases. If you have previously used the - ``changelog.exclude_commit_patterns`` functionality to ignore merge commit messages then you will - want this setting set to ``true`` to improve parsing speed. You can also now remove the merge - commit exclude pattern from the list as well to improve parsing speed. If this functionality is - not desired, you will need to update your configuration to change the new setting to ``false``. - -* **parser-scipy**: The configuration setting ``commit_parser_options.parse_squash_commits`` is now - set to ``true`` by default. The feature to parse squash commits was introduced in ``v9.17.0`` and - was originally set to ``false`` to prevent unexpected results on a non-breaking update. The parse - squash commits feature attempts to find additional commits of the same commit type within the body - of a single commit message. When squash commits are found, Python Semantic Release will separate - out each commit into its own artificial commit object and parse them individually. This - potentially can change the resulting version bump if a larger bump was detected within the - squashed components. It also allows for the changelog and release notes to separately order and - display each commit as originally written. If this is not desired, you will need to update your +* **parser-conventional, parser-emoji, parser-scipy**: Any release notice footer messages that the + commit parser detects will now be removed from the ``commit.descriptions[]`` list but maintained + in and only in the ``commit.notices[]`` list. Previously, the descriptions included all text from + the commit message but that was redundant as the default changelog now handles release notice + footers in its own section. + +* **parser-conventional, parser-emoji, parser-scipy**: Generally, a pull request or merge request + number reference is included in the subject line at the end within parentheses on some common + VCS's like GitHub. PSR now looks for this reference and extracts it into the + ``commit.linked_merge_request`` and the ``commit.linked_pull_request`` attributes of a commit + object. Since this is now pulled out individually, it is cleaner to remove this from the first + line of the ``commit.descriptions`` list (ie. the subject line) so that changelog macros do not + have to replace the text but instead only append a PR/MR link to the end of the line. The + reference does maintain the PR/MR prefix indicator (`#` or ``!``). + +* **parser-conventional, parser-emoji, parser-scipy**: The configuration setting + ``commit_parser_options.ignore_merge_commits`` is now set to ``true`` by default. The feature to + ignore squash commits was introduced in ``v9.18.0`` and was originally set to ``false`` to + prevent unexpected results on a non-breaking update. The ignore merge commits feature prevents + additional unnecessary processing on a commit message that likely will not match a commit message + syntax. Most merge commits are syntactically pre-defined by Git or Remote Version Control System + (ex. GitHub, etc.) and do not follow a commit convention (nor should they). The larger issue with + merge commits is that they ultimately are a full copy of all the changes that were previously + created and committed. The merge commit itself ensures that the previous commit tree is + maintained in history, therefore the commit message always exists. If merge commits are parsed, + it generally creates duplicate messages that will end up in your changelog, which is less than + desired in most cases. If you have previously used the ``changelog.exclude_commit_patterns`` + functionality to ignore merge commit messages then you will want this setting set to ``true`` to + improve parsing speed. You can also now remove the merge commit exclude pattern from the list as + well to improve parsing speed. If this functionality is not desired, you will need to update your configuration to change the new setting to ``false``. +* **parser-conventional, parser-emoji, parser-scipy**: The configuration setting + ``commit_parser_options.parse_squash_commits`` is now set to ``true`` by default. The feature to + parse squash commits was introduced in ``v9.17.0`` and was originally set to ``false`` to prevent + unexpected results on a non-breaking update. The parse squash commits feature attempts to find + additional commits of the same commit type within the body of a single commit message. When + squash commits are found, Python Semantic Release will separate out each commit into its own + artificial commit object and parse them individually. This potentially can change the resulting + version bump if a larger bump was detected within the squashed components. It also allows for the + changelog and release notes to separately order and display each commit as originally written. If + this is not desired, you will need to update your configuration to change the new setting to + ``false``. + .. _#733: https://github.com/python-semantic-release/python-semantic-release/issues/733 .. _080e4bc: https://github.com/python-semantic-release/python-semantic-release/commit/080e4bcb14048a2dd10445546a7ee3159b3ab85c .. _0bed906: https://github.com/python-semantic-release/python-semantic-release/commit/0bed9069df67ae806ad0a15f8434ac4efcc6ba31 diff --git a/docs/api/commands.rst b/docs/api/commands.rst index d99a40152..344c7f5f4 100644 --- a/docs/api/commands.rst +++ b/docs/api/commands.rst @@ -496,4 +496,4 @@ corresponding release is found in the remote VCS, then Python Semantic Release w attempt to create one. If using this option, the relevant authentication token *must* be supplied via the -relevant environment variable. For more information, see :ref:`index-creating-vcs-releases`. +relevant environment variable. diff --git a/docs/configuration/automatic-releases/travis.rst b/docs/configuration/automatic-releases/travis.rst index 5be380975..60ee68ce8 100644 --- a/docs/configuration/automatic-releases/travis.rst +++ b/docs/configuration/automatic-releases/travis.rst @@ -18,7 +18,7 @@ You will need to set up an environment variable in Travis. An easy way to do tha is to go to the settings page for your package and add it there. Make sure that the secret toggle is set correctly. -You need to set the :ref:`GH_TOKEN ` environment +You need to set the :ref:`GH_TOKEN ` environment variable with a personal access token for Github. It will need either ``repo`` or ``public_repo`` scope depending on whether the repository is private or public. From bba903ce21306059aebc5c0e8cd4e677a808ff7e Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 03:34:06 -0600 Subject: [PATCH 099/120] ci(deps): bump `python-semantic-release@v9.21.0` action to `v10.0.0` --- .github/workflows/cicd.yml | 4 ++-- .github/workflows/validate.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 358dff3ab..2dc956b73 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -145,10 +145,10 @@ jobs: - name: Release | Python Semantic Release id: release - uses: python-semantic-release/python-semantic-release@v9.21.1 + uses: python-semantic-release/python-semantic-release@092ace20f4ebed6a656da54b499076f1a5b803c8 # v10.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - root_options: "-v" + verbosity: 1 build: false - name: Release | Add distribution artifacts to GitHub Release Assets diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a55e468fd..1e93ff0d6 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -112,10 +112,10 @@ jobs: - name: Build | Build next version artifacts id: version - uses: python-semantic-release/python-semantic-release@v9.21.1 + uses: python-semantic-release/python-semantic-release@092ace20f4ebed6a656da54b499076f1a5b803c8 # v10.0.0 with: github_token: "" - root_options: "-v" + verbosity: 1 build: true changelog: true commit: false From 96a9503d4e33bb23290760c79e341f60dd767761 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 May 2025 09:17:03 +0000 Subject: [PATCH 100/120] ci(deps): bump `python-semantic-release/publish-action@v9.21.1` to `v10.0.0` --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 2dc956b73..8c8b45c45 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -152,7 +152,7 @@ jobs: build: false - name: Release | Add distribution artifacts to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.21.1 + uses: python-semantic-release/publish-action@d62706ce15a7c98325c51a3e5cc789fdbe843e5a # v10.0.0 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} From 2803676cf26c52177fa98d9144934853744a22bb Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 03:53:27 -0600 Subject: [PATCH 101/120] fix(github-actions): bump the github-actions dependency to `v10.0.0` (#1255) --- src/gh_action/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gh_action/requirements.txt b/src/gh_action/requirements.txt index 1aae03384..2fabec60d 100644 --- a/src/gh_action/requirements.txt +++ b/src/gh_action/requirements.txt @@ -1 +1 @@ -python-semantic-release == 9.21.1 +python-semantic-release == 10.0.0 From 22fd6792e9de082f2769abf1069ac62250ff33a0 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sat, 17 May 2025 16:45:12 -0600 Subject: [PATCH 102/120] chore(changelog): update PSR template after v10 strips PR numbers from subject lines --- config/release-templates/.components/macros.md.j2 | 11 +++++------ config/release-templates/.components/macros.rst.j2 | 5 +---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/config/release-templates/.components/macros.md.j2 b/config/release-templates/.components/macros.md.j2 index 7332fc741..89cd84cb2 100644 --- a/config/release-templates/.components/macros.md.j2 +++ b/config/release-templates/.components/macros.md.j2 @@ -58,13 +58,12 @@ ) %}{# #}{% if commit.linked_merge_request != "" -%}{% set pr_num = commit.linked_merge_request -%}{# # TODO: breaking change v10, remove summary line replacers as PSR will do it for us -#}{% set summary_line = summary_line | replace("(pull request ", "(") | replace("(" ~ pr_num ~ ")", "") | trim -%}{# - # # Add PR references with a link to the PR +%}{# # Add PR references with a link to the PR #}{% set _ = link_references.append( - format_link(pr_num | pull_request_url, "PR" ~ pr_num) + format_link( + commit.linked_merge_request | pull_request_url, + "PR" ~ commit.linked_merge_request + ) ) %}{% endif %}{# diff --git a/config/release-templates/.components/macros.rst.j2 b/config/release-templates/.components/macros.rst.j2 index a0621c252..779571c02 100644 --- a/config/release-templates/.components/macros.rst.j2 +++ b/config/release-templates/.components/macros.rst.j2 @@ -131,10 +131,7 @@ %}{% endif %}{# #}{% if commit.linked_merge_request != "" -%}{# # TODO: breaking change v10, remove summary line replacers as PSR will do it for us -#}{% set summary_line = summary_line | replace("(pull request ", "(") | replace("(" ~ commit.linked_merge_request ~ ")", "") | trim -%}{# - # # Add PR references with a link to the PR +%}{# # Add PR references with a link to the PR #}{% set _ = link_references.append("`PR%s`_" | format(commit.linked_merge_request)) %}{% endif %}{# From 74192b4ff4c7e7e5f0b577e482b9e564514d3be2 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 11:01:44 -0600 Subject: [PATCH 103/120] chore(changelog): update changelog with latest template change --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ec7decbe1..bdf5c855b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -420,7 +420,7 @@ v9.19.0 (2025-02-10) * Update references to Angular parser to Conventional Commit Parser (`PR#1177`_, `27ddf84`_) -πŸ’‘ ADDITIONAL RELEASE INFORMATION +πŸ’‘ Additional Release Information --------------------------------- * **parser-conventional**: The 'angular' commit parser has been renamed to 'conventional' to match From ceca7615915411ba843c14db135f5fac79fa8afc Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 11:31:05 -0600 Subject: [PATCH 104/120] chore(changelog): add reference to the v10 migration guide --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bdf5c855b..c2f11439b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -113,6 +113,12 @@ v10.0.0 (2025-05-25) πŸ’₯ Breaking Changes ------------------- +.. seealso:: + *For a summarized walkthrough, check out our* |v10 migration guide|_ *as well.* + +.. _v10 migration guide: ../upgrading/10-upgrade.html +.. |v10 migration guide| replace:: *v10 migration guide* + * **changelog-md**: The default Markdown changelog template and release notes template will no longer print out the entire commit message contents, instead, it will only print the commit subject line. This comes to meet the high demand of better formatted changelogs and requests for From 613640fd2cae3a90203537bcaed0077721d8dd76 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 11:52:33 -0600 Subject: [PATCH 105/120] chore(release-notes): update templates to support `first_release` creation --- .../.components/first_release.md.j2 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 config/release-templates/.components/first_release.md.j2 diff --git a/config/release-templates/.components/first_release.md.j2 b/config/release-templates/.components/first_release.md.j2 new file mode 100644 index 000000000..d0e44f7cc --- /dev/null +++ b/config/release-templates/.components/first_release.md.j2 @@ -0,0 +1,18 @@ +{# EXAMPLE: + +## vX.X.X (YYYY-MMM-DD) + +_This release is published under the MIT License._ # Release Notes Only + +- Initial Release + +#}{{ +"## %s (%s)\n" | format( + release.version.as_semver_tag(), + release.tagged_date.strftime("%Y-%m-%d") +) +}}{% if license_name is defined and license_name +%}{{ "\n_This release is published under the %s License._\n" | format(license_name) +}}{% endif +%} +- Initial Release From d099c20aad32201f1609a35765ca26f6562f4c6e Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 14:15:50 -0600 Subject: [PATCH 106/120] test(fixtures): always run e2e tests in very verbose mode for test failure debugging (#1258) * test(e2e): update tests to ignore logging messages when validating stderr output --- tests/conftest.py | 3 +- tests/e2e/cmd_version/test_version.py | 7 +++-- tests/e2e/cmd_version/test_version_print.py | 31 +++++++++++++------- tests/e2e/cmd_version/test_version_strict.py | 7 +++-- tests/e2e/conftest.py | 21 ++++++++++++- tests/e2e/test_main.py | 17 ++++++++--- 6 files changed, 66 insertions(+), 20 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 16298e98b..2d081f62b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -207,10 +207,11 @@ def _run_cli( cli_runner = CliRunner(mix_stderr=False) env_vars = {**clean_os_environment, **(env or {})} + args = ["-vv", *(argv or [])] with mock.patch.dict(os.environ, env_vars, clear=True): # run the CLI with the provided arguments - return cli_runner.invoke(main, args=(argv or []), **(invoke_kwargs or {})) + return cli_runner.invoke(main, args=args, **(invoke_kwargs or {})) return _run_cli diff --git a/tests/e2e/cmd_version/test_version.py b/tests/e2e/cmd_version/test_version.py index 5cb9700cf..0af19f8a3 100644 --- a/tests/e2e/cmd_version/test_version.py +++ b/tests/e2e/cmd_version/test_version.py @@ -26,6 +26,7 @@ from requests_mock import Mocker from tests.conftest import RunCliFn + from tests.e2e.conftest import StripLoggingMessagesFn from tests.fixtures.example_project import GetWheelFileFn, UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuiltRepoResult, GetVersionsFromRepoBuildDefFn @@ -151,6 +152,7 @@ def test_version_on_nonrelease_branch( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): """ Given repo is on a non-release branch, @@ -175,7 +177,7 @@ def test_version_on_nonrelease_branch( # Evaluate (expected -> actual) assert_successful_exit_code(result, cli_cmd) assert not result.stdout - assert expected_error_msg == result.stderr + assert expected_error_msg == strip_logging_messages(result.stderr) # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) tags_after = sorted([tag.name for tag in repo.tags]) @@ -196,6 +198,7 @@ def test_version_on_last_release( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): """ Given repo is on the last release version, @@ -229,7 +232,7 @@ def test_version_on_last_release( # Evaluate (expected -> actual) assert_successful_exit_code(result, cli_cmd) assert f"{latest_release_version}\n" == result.stdout - assert f"{expected_error_msg}\n" == result.stderr + assert f"{expected_error_msg}\n" == strip_logging_messages(result.stderr) # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) assert repo_status_before == repo_status_after diff --git a/tests/e2e/cmd_version/test_version_print.py b/tests/e2e/cmd_version/test_version_print.py index 4efd1e02f..18c0fc5f7 100644 --- a/tests/e2e/cmd_version/test_version_print.py +++ b/tests/e2e/cmd_version/test_version_print.py @@ -35,6 +35,7 @@ from requests_mock import Mocker from tests.conftest import RunCliFn + from tests.e2e.conftest import StripLoggingMessagesFn from tests.fixtures.git_repo import ( BuiltRepoResult, GetCfgValueFromDefFn, @@ -442,6 +443,7 @@ def test_version_print_last_released_prints_version( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] latest_release_version = get_versions_from_repo_build_def( @@ -465,7 +467,7 @@ def test_version_print_last_released_prints_version( # Evaluate assert_successful_exit_code(result, cli_cmd) - assert not result.stderr + assert not strip_logging_messages(result.stderr) assert f"{latest_release_version}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -493,6 +495,7 @@ def test_version_print_last_released_prints_released_if_commits( mocked_git_push: MagicMock, post_mocker: Mocker, file_in_repo: str, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] latest_release_version = get_versions_from_repo_build_def( @@ -520,7 +523,7 @@ def test_version_print_last_released_prints_released_if_commits( # Evaluate assert_successful_exit_code(result, cli_cmd) - assert not result.stderr + assert not strip_logging_messages(result.stderr) assert f"{latest_release_version}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -585,6 +588,7 @@ def test_version_print_last_released_on_detached_head( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] latest_release_version = get_versions_from_repo_build_def( @@ -611,7 +615,7 @@ def test_version_print_last_released_on_detached_head( # Evaluate (expected -> actual) assert_successful_exit_code(result, cli_cmd) - assert not result.stderr + assert not strip_logging_messages(result.stderr) assert f"{latest_release_version}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -632,6 +636,7 @@ def test_version_print_last_released_on_nonrelease_branch( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] latest_release_version = get_versions_from_repo_build_def( @@ -658,7 +663,7 @@ def test_version_print_last_released_on_nonrelease_branch( # Evaluate (expected -> actual) assert_successful_exit_code(result, cli_cmd) - assert not result.stderr + assert not strip_logging_messages(result.stderr) assert f"{latest_release_version}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -688,6 +693,7 @@ def test_version_print_last_released_tag_prints_correct_tag( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] repo_def = repo_result["definition"] @@ -712,7 +718,7 @@ def test_version_print_last_released_tag_prints_correct_tag( # Evaluate assert_successful_exit_code(result, cli_cmd) - assert not result.stderr + assert not strip_logging_messages(result.stderr) assert f"{latest_release_tag}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -748,6 +754,7 @@ def test_version_print_last_released_tag_prints_released_if_commits( mocked_git_push: MagicMock, post_mocker: Mocker, file_in_repo: str, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] repo_def = repo_result["definition"] @@ -776,7 +783,7 @@ def test_version_print_last_released_tag_prints_released_if_commits( # Evaluate assert_successful_exit_code(result, cli_cmd) - assert not result.stderr + assert not strip_logging_messages(result.stderr) assert f"{latest_release_tag}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -850,6 +857,7 @@ def test_version_print_last_released_tag_on_detached_head( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] repo_def = repo_result["definition"] @@ -877,7 +885,7 @@ def test_version_print_last_released_tag_on_detached_head( # Evaluate (expected -> actual) assert_successful_exit_code(result, cli_cmd) - assert not result.stderr + assert not strip_logging_messages(result.stderr) assert f"{latest_release_tag}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -907,6 +915,7 @@ def test_version_print_last_released_tag_on_nonrelease_branch( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] repo_def = repo_result["definition"] @@ -934,7 +943,7 @@ def test_version_print_last_released_tag_on_nonrelease_branch( # Evaluate (expected -> actual) assert_successful_exit_code(result, cli_cmd) - assert not result.stderr + assert not strip_logging_messages(result.stderr) assert f"{last_release_tag}\n" == result.stdout # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) @@ -961,6 +970,7 @@ def test_version_print_next_version_fails_on_detached_head( get_commit_def_fn: GetCommitDefFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] expected_error_msg = ( @@ -994,7 +1004,7 @@ def test_version_print_next_version_fails_on_detached_head( # Evaluate (expected -> actual) assert_exit_code(1, result, cli_cmd) assert not result.stdout - assert f"{expected_error_msg}\n" == result.stderr + assert f"{expected_error_msg}\n" == strip_logging_messages(result.stderr) # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) assert repo_status_before == repo_status_after @@ -1020,6 +1030,7 @@ def test_version_print_next_tag_fails_on_detached_head( get_commit_def_fn: GetCommitDefFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): repo = repo_result["repo"] expected_error_msg = ( @@ -1053,7 +1064,7 @@ def test_version_print_next_tag_fails_on_detached_head( # Evaluate (expected -> actual) assert_exit_code(1, result, cli_cmd) assert not result.stdout - assert f"{expected_error_msg}\n" == result.stderr + assert f"{expected_error_msg}\n" == strip_logging_messages(result.stderr) # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) assert repo_status_before == repo_status_after diff --git a/tests/e2e/cmd_version/test_version_strict.py b/tests/e2e/cmd_version/test_version_strict.py index 951a9966f..a41998ded 100644 --- a/tests/e2e/cmd_version/test_version_strict.py +++ b/tests/e2e/cmd_version/test_version_strict.py @@ -17,6 +17,7 @@ from requests_mock import Mocker from tests.conftest import RunCliFn + from tests.e2e.conftest import StripLoggingMessagesFn from tests.fixtures.git_repo import BuiltRepoResult, GetVersionsFromRepoBuildDefFn @@ -30,6 +31,7 @@ def test_version_already_released_when_strict( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): """ Given repo has no new changes since the last release, @@ -60,7 +62,7 @@ def test_version_already_released_when_strict( # Evaluate assert_exit_code(2, result, cli_cmd) assert f"{latest_release_version}\n" == result.stdout - assert f"{expected_error_msg}\n" == result.stderr + assert f"{expected_error_msg}\n" == strip_logging_messages(result.stderr) # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) assert repo_status_before == repo_status_after @@ -78,6 +80,7 @@ def test_version_on_nonrelease_branch_when_strict( run_cli: RunCliFn, mocked_git_push: MagicMock, post_mocker: Mocker, + strip_logging_messages: StripLoggingMessagesFn, ): """ Given repo is on a non-release branch, @@ -103,7 +106,7 @@ def test_version_on_nonrelease_branch_when_strict( # Evaluate assert_exit_code(2, result, cli_cmd) assert not result.stdout - assert expected_error_msg == result.stderr + assert expected_error_msg == strip_logging_messages(result.stderr) # assert nothing else happened (no code changes, no commit, no tag, no push, no vcs release) tags_after = sorted([tag.name for tag in repo.tags]) diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index b64d5aecf..209f3654e 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -2,7 +2,7 @@ import os from pathlib import Path -from re import IGNORECASE, compile as regexp +from re import IGNORECASE, MULTILINE, compile as regexp from typing import TYPE_CHECKING from unittest.mock import MagicMock @@ -47,6 +47,9 @@ class RetrieveRuntimeContextFn(Protocol): def __call__(self, repo: Repo) -> RuntimeContext: ... + class StripLoggingMessagesFn(Protocol): + def __call__(self, log: str) -> str: ... + @pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: @@ -116,6 +119,22 @@ def _retrieve_runtime_context(repo: Repo) -> RuntimeContext: return _retrieve_runtime_context +@pytest.fixture(scope="session") +def strip_logging_messages() -> StripLoggingMessagesFn: + """Fixture to strip logging messages from the output.""" + # Log levels match SemanticReleaseLogLevel enum values + logger_msg_pattern = regexp( + r"^\s*(?:\[\d\d:\d\d:\d\d\])?\s*(FATAL|CRITICAL|ERROR|WARNING|INFO|DEBUG|SILLY).*?\n(?:\s+\S.*?\n)*(?!\n[ ]{11})", + MULTILINE, + ) + + def _strip_logging_messages(log: str) -> str: + # Make sure it ends with a newline + return logger_msg_pattern.sub("", log.rstrip("\n") + "\n") + + return _strip_logging_messages + + @pytest.fixture(scope="session") def long_hash_pattern() -> Pattern: return regexp(r"\b([0-9a-f]{40})\b", IGNORECASE) diff --git a/tests/e2e/test_main.py b/tests/e2e/test_main.py index ff290146e..8ce3c58a5 100644 --- a/tests/e2e/test_main.py +++ b/tests/e2e/test_main.py @@ -8,6 +8,7 @@ import git import pytest +from click.testing import CliRunner from pytest_lazy_fixtures.lazy_fixture import lf as lazy_fixture from semantic_release import __version__ @@ -20,6 +21,7 @@ from pathlib import Path from tests.conftest import RunCliFn + from tests.e2e.conftest import StripLoggingMessagesFn from tests.fixtures.example_project import ExProjectDir, UpdatePyprojectTomlFn from tests.fixtures.git_repo import BuiltRepoResult @@ -59,8 +61,13 @@ def test_main_prints_version_and_exits(run_cli: RunCliFn): assert result.output == f"semantic-release, version {__version__}\n" -def test_main_no_args_prints_help_text(run_cli: RunCliFn): - assert_successful_exit_code(run_cli(), [MAIN_PROG_NAME]) +def test_main_no_args_passes_w_help_text(): + from semantic_release.cli.commands.main import main + + cli_cmd = [MAIN_PROG_NAME] + result = CliRunner().invoke(main, prog_name=cli_cmd[0]) + assert_successful_exit_code(result, cli_cmd) + assert "Usage: " in result.output @pytest.mark.parametrize( @@ -210,7 +217,9 @@ def test_errors_when_config_file_does_not_exist_and_passed_explicitly( @pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__) def test_errors_when_config_file_invalid_configuration( - run_cli: RunCliFn, update_pyproject_toml: UpdatePyprojectTomlFn + run_cli: RunCliFn, + update_pyproject_toml: UpdatePyprojectTomlFn, + strip_logging_messages: StripLoggingMessagesFn, ): # Setup update_pyproject_toml("tool.semantic_release.remote.type", "invalidType") @@ -220,7 +229,7 @@ def test_errors_when_config_file_invalid_configuration( result = run_cli(cli_cmd[1:]) # preprocess results - stderr_lines = result.stderr.splitlines() + stderr_lines = strip_logging_messages(result.stderr).splitlines() # Evaluate assert_exit_code(1, result, cli_cmd) From b15f3cb1a0529a32eb149ce25d9a60ca4448b88e Mon Sep 17 00:00:00 2001 From: semantic-release Date: Sun, 25 May 2025 20:32:16 +0000 Subject: [PATCH 107/120] 10.0.1 Automatically generated by python-semantic-release --- CHANGELOG.rst | 15 +++++++++++++++ .../automatic-releases/github-actions.rst | 18 +++++++++--------- pyproject.toml | 2 +- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c2f11439b..6df94b9f6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,21 @@ CHANGELOG ========= +.. _changelog-v10.0.1: + +v10.0.1 (2025-05-25) +==================== + +πŸͺ² Bug Fixes +------------ + +* **github-actions**: Bump the github-actions dependency to ``v10.0.0`` (#1255) (`PR#1255`_, + `2803676`_) + +.. _2803676: https://github.com/python-semantic-release/python-semantic-release/commit/2803676cf26c52177fa98d9144934853744a22bb +.. _PR#1255: https://github.com/python-semantic-release/python-semantic-release/pull/1255 + + .. _changelog-v10.0.0: v10.0.0 (2025-05-25) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index d8a8bd012..7c2750491 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -382,7 +382,7 @@ before the :ref:`version ` subcommand. .. code:: yaml - - uses: python-semantic-release/python-semantic-release@v10.0.0 + - uses: python-semantic-release/python-semantic-release@v10.0.1 with: root_options: "-vv --noop" @@ -699,7 +699,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v10.0.0 + - uses: python-semantic-release/publish-action@v10.0.1 with: root_options: "-vv --noop" @@ -873,14 +873,14 @@ to the GitHub Release Assets as well. - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v10.0.0 + uses: python-semantic-release/python-semantic-release@v10.0.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.0 + uses: python-semantic-release/publish-action@v10.0.1 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -979,7 +979,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@v10.0.0 + uses: python-semantic-release/python-semantic-release@v10.0.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -1038,14 +1038,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v10.0.0 + uses: python-semantic-release/python-semantic-release@v10.0.1 with: directory: ${{ env.SUBMODULE_1_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release submodule 2 id: release-submod-2 - uses: python-semantic-release/python-semantic-release@v10.0.0 + uses: python-semantic-release/python-semantic-release@v10.0.1 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1057,7 +1057,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.0 + uses: python-semantic-release/publish-action@v10.0.1 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -1065,7 +1065,7 @@ Publish Action. tag: ${{ steps.release-submod-1.outputs.tag }} - name: Publish | Upload package 2 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.0 + uses: python-semantic-release/publish-action@v10.0.1 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/pyproject.toml b/pyproject.toml index ec0bc1f8e..d14279384 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "10.0.0" +version = "10.0.1" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } From 3b7b821b6ebbdc1866c8f9852f1c6967e86cd898 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 14:37:17 -0600 Subject: [PATCH 108/120] Revert "10.0.1" This reverts commit b15f3cb1a0529a32eb149ce25d9a60ca4448b88e. Revert commit because release & deploy process failed in CI. Must revert. --- CHANGELOG.rst | 15 --------------- .../automatic-releases/github-actions.rst | 18 +++++++++--------- pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6df94b9f6..c2f11439b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,21 +4,6 @@ CHANGELOG ========= -.. _changelog-v10.0.1: - -v10.0.1 (2025-05-25) -==================== - -πŸͺ² Bug Fixes ------------- - -* **github-actions**: Bump the github-actions dependency to ``v10.0.0`` (#1255) (`PR#1255`_, - `2803676`_) - -.. _2803676: https://github.com/python-semantic-release/python-semantic-release/commit/2803676cf26c52177fa98d9144934853744a22bb -.. _PR#1255: https://github.com/python-semantic-release/python-semantic-release/pull/1255 - - .. _changelog-v10.0.0: v10.0.0 (2025-05-25) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index 7c2750491..d8a8bd012 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -382,7 +382,7 @@ before the :ref:`version ` subcommand. .. code:: yaml - - uses: python-semantic-release/python-semantic-release@v10.0.1 + - uses: python-semantic-release/python-semantic-release@v10.0.0 with: root_options: "-vv --noop" @@ -699,7 +699,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v10.0.1 + - uses: python-semantic-release/publish-action@v10.0.0 with: root_options: "-vv --noop" @@ -873,14 +873,14 @@ to the GitHub Release Assets as well. - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v10.0.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.1 + uses: python-semantic-release/publish-action@v10.0.0 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -979,7 +979,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@v10.0.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -1038,14 +1038,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v10.0.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: directory: ${{ env.SUBMODULE_1_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release submodule 2 id: release-submod-2 - uses: python-semantic-release/python-semantic-release@v10.0.1 + uses: python-semantic-release/python-semantic-release@v10.0.0 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1057,7 +1057,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.1 + uses: python-semantic-release/publish-action@v10.0.0 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -1065,7 +1065,7 @@ Publish Action. tag: ${{ steps.release-submod-1.outputs.tag }} - name: Publish | Upload package 2 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.1 + uses: python-semantic-release/publish-action@v10.0.0 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/pyproject.toml b/pyproject.toml index d14279384..ec0bc1f8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "10.0.1" +version = "10.0.0" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } From 74f72606b7a1fe9b67ff6f6d184b77222b2473e4 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 15:31:41 -0600 Subject: [PATCH 109/120] ci(deps): force `python-semantic-release` action to have `v10.0.0` --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 8c8b45c45..6fa69a102 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -145,7 +145,7 @@ jobs: - name: Release | Python Semantic Release id: release - uses: python-semantic-release/python-semantic-release@092ace20f4ebed6a656da54b499076f1a5b803c8 # v10.0.0 + uses: python-semantic-release/python-semantic-release@2803676cf26c52177fa98d9144934853744a22bb with: github_token: ${{ secrets.GITHUB_TOKEN }} verbosity: 1 From f92899174544414d915973cd574418bf7268ca1b Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 15:45:28 -0600 Subject: [PATCH 110/120] ci(release): add filesystem check post psr action to detect error --- .github/workflows/cicd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 6fa69a102..50286d5bc 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -165,6 +165,7 @@ jobs: GIT_COMMITTER_NAME: ${{ env.GITHUB_ACTIONS_AUTHOR_NAME }} GIT_COMMITTER_EMAIL: ${{ env.GITHUB_ACTIONS_AUTHOR_EMAIL }} run: | + ls -la .git/ MINOR_VERSION_TAG="$(echo "$FULL_VERSION_TAG" | cut -d. -f1,2)" git tag --force --annotate "$MINOR_VERSION_TAG" "${FULL_VERSION_TAG}^{}" -m "$MINOR_VERSION_TAG" git push -u origin "$MINOR_VERSION_TAG" --force From 917a2c730cb8f6c8cd3d00f23c876d724a4a844c Mon Sep 17 00:00:00 2001 From: semantic-release Date: Sun, 25 May 2025 22:02:48 +0000 Subject: [PATCH 111/120] 10.0.1 Automatically generated by python-semantic-release --- CHANGELOG.rst | 14 ++++++++++++++ .../automatic-releases/github-actions.rst | 18 +++++++++--------- pyproject.toml | 2 +- src/gh_action/requirements.txt | 2 +- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c2f11439b..cecf469dc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,20 @@ CHANGELOG ========= +.. _changelog-v10.0.1: + +v10.0.1 (2025-05-25) +==================== + +πŸͺ² Bug Fixes +------------ + +* **github-actions**: Bump the github-actions dependency to ``v10.0.0`` (`PR#1255`_, `2803676`_) + +.. _2803676: https://github.com/python-semantic-release/python-semantic-release/commit/2803676cf26c52177fa98d9144934853744a22bb +.. _PR#1255: https://github.com/python-semantic-release/python-semantic-release/pull/1255 + + .. _changelog-v10.0.0: v10.0.0 (2025-05-25) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index d8a8bd012..7c2750491 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -382,7 +382,7 @@ before the :ref:`version ` subcommand. .. code:: yaml - - uses: python-semantic-release/python-semantic-release@v10.0.0 + - uses: python-semantic-release/python-semantic-release@v10.0.1 with: root_options: "-vv --noop" @@ -699,7 +699,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v10.0.0 + - uses: python-semantic-release/publish-action@v10.0.1 with: root_options: "-vv --noop" @@ -873,14 +873,14 @@ to the GitHub Release Assets as well. - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v10.0.0 + uses: python-semantic-release/python-semantic-release@v10.0.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.0 + uses: python-semantic-release/publish-action@v10.0.1 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -979,7 +979,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@v10.0.0 + uses: python-semantic-release/python-semantic-release@v10.0.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -1038,14 +1038,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v10.0.0 + uses: python-semantic-release/python-semantic-release@v10.0.1 with: directory: ${{ env.SUBMODULE_1_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release submodule 2 id: release-submod-2 - uses: python-semantic-release/python-semantic-release@v10.0.0 + uses: python-semantic-release/python-semantic-release@v10.0.1 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1057,7 +1057,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.0 + uses: python-semantic-release/publish-action@v10.0.1 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -1065,7 +1065,7 @@ Publish Action. tag: ${{ steps.release-submod-1.outputs.tag }} - name: Publish | Upload package 2 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.0 + uses: python-semantic-release/publish-action@v10.0.1 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/pyproject.toml b/pyproject.toml index ec0bc1f8e..d14279384 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "10.0.0" +version = "10.0.1" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } diff --git a/src/gh_action/requirements.txt b/src/gh_action/requirements.txt index 2fabec60d..574893c77 100644 --- a/src/gh_action/requirements.txt +++ b/src/gh_action/requirements.txt @@ -1 +1 @@ -python-semantic-release == 10.0.0 +python-semantic-release == 10.0.1 From ffb2976edd5d9a04e4c6c63b885484814b86879f Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 16:24:59 -0600 Subject: [PATCH 112/120] ci(deps): bump `python-semantic-release@v10.0.0` action to `v10.0.1` --- .github/workflows/cicd.yml | 2 +- .github/workflows/validate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 50286d5bc..c83674d22 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -145,7 +145,7 @@ jobs: - name: Release | Python Semantic Release id: release - uses: python-semantic-release/python-semantic-release@2803676cf26c52177fa98d9144934853744a22bb + uses: python-semantic-release/python-semantic-release@917a2c730cb8f6c8cd3d00f23c876d724a4a844c # v10.0.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} verbosity: 1 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 1e93ff0d6..053448561 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -112,7 +112,7 @@ jobs: - name: Build | Build next version artifacts id: version - uses: python-semantic-release/python-semantic-release@092ace20f4ebed6a656da54b499076f1a5b803c8 # v10.0.0 + uses: python-semantic-release/python-semantic-release@917a2c730cb8f6c8cd3d00f23c876d724a4a844c # v10.0.1 with: github_token: "" verbosity: 1 From 109b8bdd2c83122e9812c88a8f8cc25109c1068c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 May 2025 22:20:14 +0000 Subject: [PATCH 113/120] ci(deps): bump `python-semantic-release/publish-action@v10.0.0` to `v10.0.1` --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index c83674d22..e4b35a055 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -152,7 +152,7 @@ jobs: build: false - name: Release | Add distribution artifacts to GitHub Release Assets - uses: python-semantic-release/publish-action@d62706ce15a7c98325c51a3e5cc789fdbe843e5a # v10.0.0 + uses: python-semantic-release/publish-action@d3a9934c4fff57f0d4df24450566d3dba7e7082a # v10.0.1 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} From 93e23c8993fe6f113095bfcd5089684f403cc6b9 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 20:20:40 -0600 Subject: [PATCH 114/120] fix(github-actions): add filesystem UID/GID fixer after action workspace modification (#1262) * ci(validate): fix job condition on github-action testing * ci(release): remove filesystem error check detection - debug flag * ci(deps): temporarily use the current HEAD github action to release psr --- .github/workflows/cicd.yml | 3 +-- .github/workflows/validate.yml | 2 +- src/gh_action/action.sh | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index e4b35a055..f63564b7c 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -145,7 +145,7 @@ jobs: - name: Release | Python Semantic Release id: release - uses: python-semantic-release/python-semantic-release@917a2c730cb8f6c8cd3d00f23c876d724a4a844c # v10.0.1 + uses: ./ with: github_token: ${{ secrets.GITHUB_TOKEN }} verbosity: 1 @@ -165,7 +165,6 @@ jobs: GIT_COMMITTER_NAME: ${{ env.GITHUB_ACTIONS_AUTHOR_NAME }} GIT_COMMITTER_EMAIL: ${{ env.GITHUB_ACTIONS_AUTHOR_EMAIL }} run: | - ls -la .git/ MINOR_VERSION_TAG="$(echo "$FULL_VERSION_TAG" | cut -d. -f1,2)" git tag --force --annotate "$MINOR_VERSION_TAG" "${FULL_VERSION_TAG}^{}" -m "$MINOR_VERSION_TAG" git push -u origin "$MINOR_VERSION_TAG" --force diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 053448561..89535f255 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -393,7 +393,7 @@ jobs: test-gh-action: name: Validate Action Build & Execution runs-on: ubuntu-latest - if: inputs.gha-src-files-changed == 'true' || inputs.gha-test-files-changed == 'true' || inputs.ci-files-changed == 'true' + if: ${{ inputs.gha-src-files-changed == 'true' || inputs.gha-test-files-changed == 'true' || inputs.ci-files-changed == 'true' }} needs: - build diff --git a/src/gh_action/action.sh b/src/gh_action/action.sh index cd862d9a9..7ae58f68d 100644 --- a/src/gh_action/action.sh +++ b/src/gh_action/action.sh @@ -2,6 +2,8 @@ set -e +WORKSPACE_DIR="$(pwd)" + explicit_run_cmd() { local cmd="" cmd="$(printf '%s' "$*" | sed 's/^ *//g' | sed 's/ *$//g')" @@ -49,6 +51,20 @@ eval_string_input() { printf '%s' "${if_defined/\%s/$value}" } +# Capture UID and GID of the external filesystem +if [ ! -f "$WORKSPACE_DIR/.git/HEAD" ]; then + echo "::error:: .git/HEAD file not found. Ensure you are in a valid git repository." + exit 1 +fi + +EXT_HOST_UID="$(stat -c '%u' "$WORKSPACE_DIR/.git/HEAD")" +EXT_HOST_GID="$(stat -c '%g' "$WORKSPACE_DIR/.git/HEAD")" + +if [ -z "$EXT_HOST_UID" ] || [ -z "$EXT_HOST_GID" ]; then + echo "Error: Unable to determine external filesystem UID/GID from .git/HEAD" + exit 1 +fi + # Convert inputs to command line arguments ROOT_OPTIONS=() @@ -165,5 +181,9 @@ export GH_TOKEN="${INPUT_GITHUB_TOKEN}" # normalize extra spaces into single spaces as you combine the arguments CMD_ARGS="$(printf '%s' "${ROOT_OPTIONS[*]} version ${ARGS[*]}" | sed 's/ [ ]*/ /g' | sed 's/^ *//g')" +# Make sure the workspace directory is owned by the external filesystem UID/GID no matter what +# This is to ensure that after the action, and a commit was created, the files are owned by the external filesystem +trap "chown -R $EXT_HOST_UID:$EXT_HOST_GID '$WORKSPACE_DIR'" EXIT + # Run Semantic Release (explicitly use the GitHub action version) explicit_run_cmd "$PSR_VENV_BIN/semantic-release $CMD_ARGS" From 1a324000f2251a9e722e77b128bf72712653813f Mon Sep 17 00:00:00 2001 From: semantic-release Date: Mon, 26 May 2025 02:36:56 +0000 Subject: [PATCH 115/120] 10.0.2 Automatically generated by python-semantic-release --- CHANGELOG.rst | 15 +++++++++++++++ .../automatic-releases/github-actions.rst | 18 +++++++++--------- pyproject.toml | 2 +- src/gh_action/requirements.txt | 2 +- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cecf469dc..09d90b45c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,21 @@ CHANGELOG ========= +.. _changelog-v10.0.2: + +v10.0.2 (2025-05-26) +==================== + +πŸͺ² Bug Fixes +------------ + +* **github-actions**: Add filesystem UID/GID fixer after action workspace modification (`PR#1262`_, + `93e23c8`_) + +.. _93e23c8: https://github.com/python-semantic-release/python-semantic-release/commit/93e23c8993fe6f113095bfcd5089684f403cc6b9 +.. _PR#1262: https://github.com/python-semantic-release/python-semantic-release/pull/1262 + + .. _changelog-v10.0.1: v10.0.1 (2025-05-25) diff --git a/docs/configuration/automatic-releases/github-actions.rst b/docs/configuration/automatic-releases/github-actions.rst index 7c2750491..4fc5022af 100644 --- a/docs/configuration/automatic-releases/github-actions.rst +++ b/docs/configuration/automatic-releases/github-actions.rst @@ -382,7 +382,7 @@ before the :ref:`version ` subcommand. .. code:: yaml - - uses: python-semantic-release/python-semantic-release@v10.0.1 + - uses: python-semantic-release/python-semantic-release@v10.0.2 with: root_options: "-vv --noop" @@ -699,7 +699,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v10.0.1 + - uses: python-semantic-release/publish-action@v10.0.2 with: root_options: "-vv --noop" @@ -873,14 +873,14 @@ to the GitHub Release Assets as well. - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v10.0.1 + uses: python-semantic-release/python-semantic-release@v10.0.2 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.1 + uses: python-semantic-release/publish-action@v10.0.2 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -979,7 +979,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@v10.0.1 + uses: python-semantic-release/python-semantic-release@v10.0.2 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -1038,14 +1038,14 @@ Publish Action. - name: Release submodule 1 id: release-submod-1 - uses: python-semantic-release/python-semantic-release@v10.0.1 + uses: python-semantic-release/python-semantic-release@v10.0.2 with: directory: ${{ env.SUBMODULE_1_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release submodule 2 id: release-submod-2 - uses: python-semantic-release/python-semantic-release@v10.0.1 + uses: python-semantic-release/python-semantic-release@v10.0.2 with: directory: ${{ env.SUBMODULE_2_DIR }} github_token: ${{ secrets.GITHUB_TOKEN }} @@ -1057,7 +1057,7 @@ Publish Action. # ------------------------------------------------------------------- # - name: Publish | Upload package 1 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.1 + uses: python-semantic-release/publish-action@v10.0.2 if: steps.release-submod-1.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_1_DIR }} @@ -1065,7 +1065,7 @@ Publish Action. tag: ${{ steps.release-submod-1.outputs.tag }} - name: Publish | Upload package 2 to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.0.1 + uses: python-semantic-release/publish-action@v10.0.2 if: steps.release-submod-2.outputs.released == 'true' with: directory: ${{ env.SUBMODULE_2_DIR }} diff --git a/pyproject.toml b/pyproject.toml index d14279384..7a3aa918a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "10.0.1" +version = "10.0.2" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } diff --git a/src/gh_action/requirements.txt b/src/gh_action/requirements.txt index 574893c77..835d01792 100644 --- a/src/gh_action/requirements.txt +++ b/src/gh_action/requirements.txt @@ -1 +1 @@ -python-semantic-release == 10.0.1 +python-semantic-release == 10.0.2 From 4a148cecf59d39a5e511e0c8d05f5dbb9ad0a09c Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Sun, 25 May 2025 20:56:07 -0600 Subject: [PATCH 116/120] ci(deps): bump `python-semantic-release@v10.0.1` action to `v10.0.2` --- .github/workflows/cicd.yml | 2 +- .github/workflows/validate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index f63564b7c..2209b8bbb 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -145,7 +145,7 @@ jobs: - name: Release | Python Semantic Release id: release - uses: ./ + uses: python-semantic-release/python-semantic-release@1a324000f2251a9e722e77b128bf72712653813f # v10.0.2 with: github_token: ${{ secrets.GITHUB_TOKEN }} verbosity: 1 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 89535f255..f20aa9700 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -112,7 +112,7 @@ jobs: - name: Build | Build next version artifacts id: version - uses: python-semantic-release/python-semantic-release@917a2c730cb8f6c8cd3d00f23c876d724a4a844c # v10.0.1 + uses: python-semantic-release/python-semantic-release@1a324000f2251a9e722e77b128bf72712653813f # v10.0.2 with: github_token: "" verbosity: 1 From 55486cd9551dafe57b715c06473fdc964f74e5e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 02:54:34 +0000 Subject: [PATCH 117/120] ci(deps): bump `python-semantic-release/publish-action@v10.0.1` to `v10.0.2` --- .github/workflows/cicd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 2209b8bbb..512454ab0 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -152,7 +152,7 @@ jobs: build: false - name: Release | Add distribution artifacts to GitHub Release Assets - uses: python-semantic-release/publish-action@d3a9934c4fff57f0d4df24450566d3dba7e7082a # v10.0.1 + uses: python-semantic-release/publish-action@e5e3010f6a207cd5d6f5d3dccedbea355484ca02 # v10.0.2 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} From b1151b1d3a3e8c3ff82435d4302f481d0c99b7a1 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 26 May 2025 13:39:52 -0600 Subject: [PATCH 118/120] refactor(changelog): simplify & consolidate jinja macro functionality (#1264) * refactor(changelog-md): simplify & consolidate jinja macro functionality * refactor(changelog-rst): simplify & consolidate jinja macro functionality --- .../conventional/md/.components/macros.md.j2 | 189 ++++++-------- .../rst/.components/macros.rst.j2 | 235 ++++++++---------- 2 files changed, 178 insertions(+), 246 deletions(-) diff --git a/src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 b/src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 index 8bb56ea79..13cc18fac 100644 --- a/src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 +++ b/src/semantic_release/data/templates/conventional/md/.components/macros.md.j2 @@ -6,41 +6,50 @@ %} +{# + MACRO: Capitalize the first letter of a string only +#}{% macro capitalize_first_letter_only(sentence) +%}{{ (sentence[0] | upper) ~ sentence[1:] +}}{% endmacro +%} + + {# MACRO: commit message links or PR/MR links of commit #}{% macro commit_msg_links(commit) %}{% if commit.error is undefined -%}{% set commit_hash_link = format_link( - commit.hexsha | commit_hash_url, - "`%s`" | format(commit.short_hash) - ) %}{# -#}{% set summary_line = commit.descriptions[0] | safe -%}{% set summary_line = [ - summary_line.split(" ", maxsplit=1)[0] | capitalize, - summary_line.split(" ", maxsplit=1)[1] - ] | join(" ") + # # Initialize variables +#}{% set link_references = [] +%}{% set summary_line = capitalize_first_letter_only( + commit.descriptions[0] | safe + ) %}{# #}{% if commit.linked_merge_request != "" %}{# # Add PR references with a link to the PR -#}{% set pr_num = commit.linked_merge_request -%}{% set pr_link = format_link(pr_num | pull_request_url, pr_num) -%}{# - # TODO: breaking change v10, remove summary line replacers as PSR will do it for us -#}{% set summary_line = summary_line | replace("(pull request", "(") | replace("(" ~ pr_num ~ ")", "") | trim -%}{% set summary_line = "%s (%s, %s)" | format( - summary_line, - pr_link, - commit_hash_link, +#}{% set _ = link_references.append( + format_link( + commit.linked_merge_request | pull_request_url, + commit.linked_merge_request + ) ) +%}{% endif +%}{# + # # DEFAULT: Always include the commit hash as a link +#}{% set _ = link_references.append( + format_link( + commit.hexsha | commit_hash_url, + "`%s`" | format(commit.short_hash) + ) + ) %}{# - # DEFAULT: No PR identifier found, so just append commit hash as url to the commit summary_line -#}{% else -%}{% set summary_line = "%s (%s)" | format(summary_line, commit_hash_link) +#}{% set formatted_links = "" +%}{% if link_references | length > 0 +%}{% set formatted_links = " (%s)" | format(link_references | join(", ")) %}{% endif %}{# # Return the modified summary_line -#}{{ summary_line +#}{{ summary_line ~ formatted_links }}{% endif %}{% endmacro %} @@ -71,24 +80,21 @@ {# - MACRO: format the breaking changes description by: - - Capitalizing the description + MACRO: format a commit descriptions list by: + - Capitalizing the first line of the description - Adding an optional scope prefix -#}{% macro format_breaking_changes_description(commit) -%}{% set ns = namespace(full_description="") + - Joining the rest of the descriptions with a double newline +#}{% macro format_attr_paragraphs(commit, attribute) +%}{# NOTE: requires namespace because of the way Jinja2 handles variable scoping with loops +#}{% set ns = namespace(full_description="") %}{# #}{% if commit.error is undefined -%}{% for paragraph in commit.breaking_descriptions +%}{% for paragraph in commit | attr(attribute) %}{% if paragraph | trim | length > 0 %}{# -#}{% set paragraph_text = [ - paragraph.split(" ", maxsplit=1)[0] | capitalize, - paragraph.split(" ", maxsplit=1)[1] - ] | join(" ") | trim | safe -%}{# #}{% set ns.full_description = [ ns.full_description, - paragraph_text + capitalize_first_letter_only(paragraph) | trim | safe, ] | join("\n\n") %}{# #}{% endif @@ -108,65 +114,48 @@ %} +{# + MACRO: format the breaking changes description by: + - Capitalizing the description + - Adding an optional scope prefix +#}{% macro format_breaking_changes_description(commit) +%}{{ format_attr_paragraphs(commit, 'breaking_descriptions') +}}{% endmacro +%} + + {# MACRO: format the release notice by: - Capitalizing the description - Adding an optional scope prefix #}{% macro format_release_notice(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.release_notices -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = [ - paragraph.split(" ", maxsplit=1)[0] | capitalize, - paragraph.split(" ", maxsplit=1)[1] - ] | join(" ") | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim -%}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) -%}{% endif -%}{% endif -%}{# -#}{{ ns.full_description +%}{{ format_attr_paragraphs(commit, "release_notices") }}{% endmacro %} {# - MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_descriptions(ns) + MACRO: order commits alphabetically by scope and attribute + - Commits are sorted based on scope and then the attribute alphabetically + - Commits without scope are placed first and sorted alphabetically by the attribute + - parameter: ns (namespace) object with a commits list + - parameter: attr (string) attribute to sort by + - returns None but modifies the ns.commits list in place +#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) %}{% set ordered_commits = [] %}{# # # Eliminate any ParseError commits from input set #}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list %}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor + # # grab all commits with no scope and sort alphabetically by attr +#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor %}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor + # # grab all commits with a scope and sort alphabetically by the scope and then attr +#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor %}{# # # Return the ordered commits #}{% set ns.commits = ordered_commits @@ -174,6 +163,18 @@ %} +{# + MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_descriptions(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') +%}{% endmacro +%} + + {# MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes - Commits are sorted based on the commit type and the commit message @@ -181,23 +182,7 @@ - parameter: ns (namespace) object with a commits list - returns None but modifies the ns.commits list in place #}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='breaking_descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,breaking_descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') %}{% endmacro %} @@ -209,22 +194,6 @@ - parameter: ns (namespace) object with a commits list - returns None but modifies the ns.commits list in place #}{% macro apply_alphabetical_ordering_by_release_notices(ns) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='release_notices.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,release_notices.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') %}{% endmacro %} diff --git a/src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 b/src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 index f70043036..6fc7f90ff 100644 --- a/src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 +++ b/src/semantic_release/data/templates/conventional/rst/.components/macros.rst.j2 @@ -1,3 +1,11 @@ +{# + MACRO: Capitalize the first letter of a string only +#}{% macro capitalize_first_letter_only(sentence) +%}{{ (sentence[0] | upper) ~ sentence[1:] +}}{% endmacro +%} + + {# MACRO: format a post-paragraph link reference in RST #}{% macro format_link_reference(link, label) @@ -6,6 +14,49 @@ %} +{# MACRO: generate a heading underline that matches the exact length of the header #} +{% macro generate_heading_underline(header, underline_char) +%}{% set header_underline = [] +%}{% for _ in header +%}{% set __ = header_underline.append(underline_char) +%}{% endfor +%}{# # Print out the header underline +#}{{ header_underline | join +}}{% endmacro +%} + + +{# + MACRO: formats a commit message for a non-inline RST link for a commit hash and/or PR/MR +#}{% macro commit_msg_links(commit) +%}{% if commit.error is undefined +%}{# + # # Initialize variables +#}{% set link_references = [] +%}{% set summary_line = capitalize_first_letter_only( + commit.descriptions[0] | safe + ) +%}{# +#}{% if commit.linked_merge_request != "" +%}{# # Add PR/MR references with a link to the PR/MR +#}{% set _ = link_references.append("`%s`_" | format(commit.linked_merge_request)) +%}{% endif +%}{# + # DEFAULT: Always include the commit hash as a link +#}{% set _ = link_references.append("`%s`_" | format(commit.short_hash)) +%}{# +#}{% set formatted_links = "" +%}{% if link_references | length > 0 +%}{% set formatted_links = " (%s)" | format(link_references | join(", ")) +%}{% endif +%}{# + # Return the modified summary_line +#}{{ summary_line ~ formatted_links +}}{% endif +%}{% endmacro +%} + + {# MACRO: format commit summary line #}{% macro format_commit_summary_line(commit) @@ -50,72 +101,21 @@ {# - MACRO: formats a commit message for a non-inline RST link for a commit hash and/or PR/MR -#}{% macro commit_msg_links(commit, hvcs_type) -%}{% if commit.error is undefined -%}{% set commit_hash_link = "`%s`_" | format(commit.short_hash) -%}{# -#}{% set summary_line = commit.descriptions[0] | safe -%}{% set summary_line = [ - summary_line.split(" ", maxsplit=1)[0] | capitalize, - summary_line.split(" ", maxsplit=1)[1] - ] | join(" ") -%}{# -#}{% if commit.linked_merge_request != "" -%}{# # Add PR references with a link to the PR -#}{% set pr_link = "`%s`_" | format(commit.linked_merge_request) -%}{# - # TODO: breaking change v10, remove summary line replacers as PSR will do it for us -#}{% set summary_line = summary_line | replace("(pull request ", "(") | replace("(" ~ commit.linked_merge_request ~ ")", "") | trim -%}{% set summary_line = "%s (%s, %s)" | format( - summary_line, - pr_link, - commit_hash_link, - ) -%}{# - # DEFAULT: No PR identifier found, so just append a commit hash as url to the commit summary_line -#}{% else -%}{% set summary_line = "%s (%s)" | format(summary_line, commit_hash_link) -%}{% endif -%}{# - # Return the modified summary_line -#}{{ summary_line -}}{% endif -%}{% endmacro -%} - - -{# MACRO: generate a heading underline that matches the exact length of the header #} -{% macro generate_heading_underline(header, underline_char) -%}{% set header_underline = [] -%}{% for _ in header -%}{{ header_underline.append(underline_char) | default("", true) -}}{% endfor -%}{# # Print out the header underline -#}{{ header_underline | join -}}{% endmacro -%} - - -{# - MACRO: format the breaking changes description by: - - Capitalizing the description + MACRO: format a commit descriptions list by: + - Capitalizing the first line of the description - Adding an optional scope prefix -#}{% macro format_breaking_changes_description(commit) -%}{% set ns = namespace(full_description="") + - Joining the rest of the descriptions with a double newline +#}{% macro format_attr_paragraphs(commit, attribute) +%}{# NOTE: requires namespace because of the way Jinja2 handles variable scoping with loops +#}{% set ns = namespace(full_description="") %}{# #}{% if commit.error is undefined -%}{% for paragraph in commit.breaking_descriptions +%}{% for paragraph in commit | attr(attribute) %}{% if paragraph | trim | length > 0 %}{# -#}{% set paragraph_text = [ - paragraph.split(" ", maxsplit=1)[0] | capitalize, - paragraph.split(" ", maxsplit=1)[1] - ] | join(" ") | trim | safe -%}{# #}{% set ns.full_description = [ ns.full_description, - paragraph_text + capitalize_first_letter_only(paragraph) | trim | safe, ] | join("\n\n") %}{# #}{% endif @@ -135,65 +135,48 @@ %} +{# + MACRO: format the breaking changes description by: + - Capitalizing the description + - Adding an optional scope prefix +#}{% macro format_breaking_changes_description(commit) +%}{{ format_attr_paragraphs(commit, 'breaking_descriptions') +}}{% endmacro +%} + + {# MACRO: format the release notice by: - Capitalizing the description - Adding an optional scope prefix #}{% macro format_release_notice(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.release_notices -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = [ - paragraph.split(" ", maxsplit=1)[0] | capitalize, - paragraph.split(" ", maxsplit=1)[1] - ] | join(" ") | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim -%}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) -%}{% endif -%}{% endif -%}{# -#}{{ ns.full_description +%}{{ format_attr_paragraphs(commit, "release_notices") }}{% endmacro %} {# - MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_descriptions(ns) + MACRO: order commits alphabetically by scope and attribute + - Commits are sorted based on scope and then the attribute alphabetically + - Commits without scope are placed first and sorted alphabetically by the attribute + - parameter: ns (namespace) object with a commits list + - parameter: attr (string) attribute to sort by + - returns None but modifies the ns.commits list in place +#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) %}{% set ordered_commits = [] %}{# # # Eliminate any ParseError commits from input set #}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list %}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor + # # grab all commits with no scope and sort alphabetically by attr +#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor %}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor + # # grab all commits with a scope and sort alphabetically by the scope and then attr +#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor %}{# # # Return the ordered commits #}{% set ns.commits = ordered_commits @@ -201,6 +184,18 @@ %} +{# + MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_descriptions(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') +%}{% endmacro +%} + + {# MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes - Commits are sorted based on the commit type and the commit message @@ -208,23 +203,7 @@ - parameter: ns (namespace) object with a commits list - returns None but modifies the ns.commits list in place #}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='breaking_descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,breaking_descriptions.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') %}{% endmacro %} @@ -236,22 +215,6 @@ - parameter: ns (namespace) object with a commits list - returns None but modifies the ns.commits list in place #}{% macro apply_alphabetical_ordering_by_release_notices(ns) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by the first line of the commit message -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute='release_notices.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then the first line of the commit message -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute='scope,release_notices.0') -%}{{ ordered_commits.append(commit) | default("", true) -}}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') %}{% endmacro %} From 10e1b52f4b3f5ccdfa322c3005dec3381b97fa81 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 26 May 2025 13:43:11 -0600 Subject: [PATCH 119/120] chore(psr-changelog): consolidate & simplify jinja macro implementation (#1265) --- .../.components/changes.md.j2 | 13 +- .../.components/changes.rst.j2 | 17 +- .../.components/macros.common.j2 | 160 ++++++++++ .../.components/macros.md.j2 | 174 +---------- .../.components/macros.rst.j2 | 275 ++++-------------- 5 files changed, 229 insertions(+), 410 deletions(-) create mode 100644 config/release-templates/.components/macros.common.j2 diff --git a/config/release-templates/.components/changes.md.j2 b/config/release-templates/.components/changes.md.j2 index d2a062d9a..6cdef2d17 100644 --- a/config/release-templates/.components/changes.md.j2 +++ b/config/release-templates/.components/changes.md.j2 @@ -1,9 +1,10 @@ -{% from 'macros.md.j2' import apply_alphabetical_ordering_by_brk_descriptions -%}{% from 'macros.md.j2' import apply_alphabetical_ordering_by_descriptions -%}{% from 'macros.md.j2' import apply_alphabetical_ordering_by_release_notices -%}{% from 'macros.md.j2' import emoji_map, format_breaking_changes_description -%}{% from 'macros.md.j2' import format_commit_summary_line, format_release_notice -%}{% from 'macros.md.j2' import section_heading_order, section_heading_translations +{% from 'macros.common.j2' import apply_alphabetical_ordering_by_brk_descriptions +%}{% from 'macros.common.j2' import apply_alphabetical_ordering_by_descriptions +%}{% from 'macros.common.j2' import apply_alphabetical_ordering_by_release_notices +%}{% from 'macros.common.j2' import emoji_map, format_breaking_changes_description +%}{% from 'macros.common.j2' import format_release_notice, section_heading_order +%}{% from 'macros.common.j2' import section_heading_translations +%}{% from 'macros.md.j2' import format_commit_summary_line %}{# EXAMPLE: diff --git a/config/release-templates/.components/changes.rst.j2 b/config/release-templates/.components/changes.rst.j2 index 90434bfdb..9751108c2 100644 --- a/config/release-templates/.components/changes.rst.j2 +++ b/config/release-templates/.components/changes.rst.j2 @@ -1,11 +1,12 @@ -{% from 'macros.rst.j2' import apply_alphabetical_ordering_by_brk_descriptions -%}{% from 'macros.rst.j2' import apply_alphabetical_ordering_by_descriptions -%}{% from 'macros.rst.j2' import apply_alphabetical_ordering_by_release_notices -%}{% from 'macros.rst.j2' import emoji_map, extract_issue_link_references, extract_pr_link_reference -%}{% from 'macros.rst.j2' import format_breaking_changes_description, format_commit_summary_line -%}{% from 'macros.rst.j2' import format_link_reference, format_release_notice -%}{% from 'macros.rst.j2' import generate_heading_underline, section_heading_order -%}{% from 'macros.rst.j2' import section_heading_translations +{% from 'macros.common.j2' import apply_alphabetical_ordering_by_brk_descriptions +%}{% from 'macros.common.j2' import apply_alphabetical_ordering_by_descriptions +%}{% from 'macros.common.j2' import apply_alphabetical_ordering_by_release_notices +%}{% from 'macros.common.j2' import emoji_map, format_breaking_changes_description +%}{% from 'macros.common.j2' import format_release_notice, section_heading_order +%}{% from 'macros.common.j2' import section_heading_translations +%}{% from 'macros.rst.j2' import extract_issue_link_references, extract_pr_link_reference +%}{% from 'macros.rst.j2' import format_commit_summary_line, format_link_reference +%}{% from 'macros.rst.j2' import generate_heading_underline %}{# ✨ Features diff --git a/config/release-templates/.components/macros.common.j2 b/config/release-templates/.components/macros.common.j2 new file mode 100644 index 000000000..5ec7ff6d0 --- /dev/null +++ b/config/release-templates/.components/macros.common.j2 @@ -0,0 +1,160 @@ +{# TODO: move to configuration for user to modify #} +{% set section_heading_translations = { + 'feat': 'features', + 'fix': 'bug fixes', + 'perf': 'performance improvements', + 'docs': 'documentation', + 'build': 'build system', + 'refactor': 'refactoring', + 'test': 'testing', + 'ci': 'continuous integration', + 'chore': 'chores', + 'style': 'code style', + } +%} + +{% set section_heading_order = section_heading_translations.values() %} + +{% set emoji_map = { + 'breaking': 'πŸ’₯', + 'features': '✨', + 'bug fixes': 'πŸͺ²', + 'performance improvements': '⚑', + 'documentation': 'πŸ“–', + 'build system': 'βš™οΈ', + 'refactoring': '♻️', + 'testing': 'βœ…', + 'continuous integration': 'πŸ€–', + 'chores': '🧹', + 'code style': '🎨', + 'unknown': '❗', + 'release_note': 'πŸ’‘', +} %} + + +{# + MACRO: Capitalize the first letter of a string only +#}{% macro capitalize_first_letter_only(sentence) +%}{{ (sentence[0] | upper) ~ sentence[1:] +}}{% endmacro +%} + + +{# + MACRO: format a commit descriptions list by: + - Capitalizing the first line of the description + - Adding an optional scope prefix + - Joining the rest of the descriptions with a double newline +#}{% macro format_attr_paragraphs(commit, attribute) +%}{# NOTE: requires namespace because of the way Jinja2 handles variable scoping with loops +#}{% set ns = namespace(full_description="") +%}{# +#}{% if commit.error is undefined +%}{% for paragraph in commit | attr(attribute) +%}{% if paragraph | trim | length > 0 +%}{# +#}{% set ns.full_description = [ + ns.full_description, + capitalize_first_letter_only(paragraph) | trim | safe, + ] | join("\n\n") +%}{# +#}{% endif +%}{% endfor +%}{# +#}{% set ns.full_description = ns.full_description | trim +%}{# +#}{% if commit.scope +%}{% set ns.full_description = "**%s**: %s" | format( + commit.scope, ns.full_description + ) +%}{% endif +%}{% endif +%}{# +#}{{ ns.full_description +}}{% endmacro +%} + + +{# + MACRO: format the breaking changes description by: + - Capitalizing the description + - Adding an optional scope prefix +#}{% macro format_breaking_changes_description(commit) +%}{{ format_attr_paragraphs(commit, 'breaking_descriptions') +}}{% endmacro +%} + + +{# + MACRO: format the release notice by: + - Capitalizing the description + - Adding an optional scope prefix +#}{% macro format_release_notice(commit) +%}{{ format_attr_paragraphs(commit, "release_notices") +}}{% endmacro +%} + + +{# + MACRO: order commits alphabetically by scope and attribute + - Commits are sorted based on scope and then the attribute alphabetically + - Commits without scope are placed first and sorted alphabetically by the attribute + - parameter: ns (namespace) object with a commits list + - parameter: attr (string) attribute to sort by + - returns None but modifies the ns.commits list in place +#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) +%}{% set ordered_commits = [] +%}{# + # # Eliminate any ParseError commits from input set +#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list +%}{# + # # grab all commits with no scope and sort alphabetically by attr +#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor +%}{# + # # grab all commits with a scope and sort alphabetically by the scope and then attr +#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) +%}{% set _ = ordered_commits.append(commit) +%}{% endfor +%}{# + # # Return the ordered commits +#}{% set ns.commits = ordered_commits +%}{% endmacro +%} + + +{# + MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_descriptions(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') +%}{% endmacro +%} + + +{# + MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') +%}{% endmacro +%} + + +{# + MACRO: apply smart ordering of commits objects based on alphabetized release notices and then scopes + - Commits are sorted based on the commit type and the commit message + - Commits are grouped by the commit type + - parameter: ns (namespace) object with a commits list + - returns None but modifies the ns.commits list in place +#}{% macro apply_alphabetical_ordering_by_release_notices(ns) +%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') +%}{% endmacro +%} diff --git a/config/release-templates/.components/macros.md.j2 b/config/release-templates/.components/macros.md.j2 index 89cd84cb2..bbccd9c86 100644 --- a/config/release-templates/.components/macros.md.j2 +++ b/config/release-templates/.components/macros.md.j2 @@ -1,33 +1,4 @@ -{% set section_heading_translations = { - 'feat': 'features', - 'fix': 'bug fixes', - 'perf': 'performance improvements', - 'docs': 'documentation', - 'build': 'build system', - 'refactor': 'refactoring', - 'test': 'testing', - 'ci': 'continuous integration', - 'chore': 'chores', - 'style': 'code style', -} %} - -{% set section_heading_order = section_heading_translations.values() %} - -{% set emoji_map = { - 'breaking': 'πŸ’₯', - 'features': '✨', - 'bug fixes': 'πŸͺ²', - 'performance improvements': '⚑', - 'documentation': 'πŸ“–', - 'build system': 'βš™οΈ', - 'refactoring': '♻️', - 'testing': 'βœ…', - 'continuous integration': 'πŸ€–', - 'chores': '🧹', - 'code style': '🎨', - 'unknown': '❗', - 'release_note': 'πŸ’‘', -} %} +{% from 'macros.common.j2' import capitalize_first_letter_only %} {# @@ -38,14 +9,6 @@ %} -{# - MACRO: Capitalize the first letter of a string only -#}{% macro capitalize_first_letter_only(sentence) -%}{{ (sentence[0] | upper) ~ sentence[1:] -}}{% endmacro -%} - - {# MACRO: commit message links or PR/MR links of commit #}{% macro commit_msg_links(commit) @@ -109,138 +72,3 @@ }}{% endif %}{% endmacro %} - - -{# - MACRO: format the breaking changes description by: - - Capitalizing the description - - Adding an optional scope prefix -#}{% macro format_breaking_changes_description(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.breaking_descriptions -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = capitalize_first_letter_only(paragraph) | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim -%}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) -%}{% endif -%}{% endif -%}{# -#}{{ ns.full_description -}}{% endmacro -%} - - -{# - MACRO: format the release notice by: - - Capitalizing the description - - Adding an optional scope prefix -#}{% macro format_release_notice(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.release_notices -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = capitalize_first_letter_only(paragraph) | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim -%}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) -%}{% endif -%}{% endif -%}{# -#}{{ ns.full_description -}}{% endmacro -%} - - -{# - MACRO: order commits alphabetically by scope and attribute - - Commits are sorted based on scope and then the attribute alphabetically - - Commits without scope are placed first and sorted alphabetically by the attribute - - parameter: ns (namespace) object with a commits list - - parameter: attr (string) attribute to sort by - - returns None but modifies the ns.commits list in place -#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by attr -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) -%}{% set _ = ordered_commits.append(commit) -%}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then attr -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) -%}{% set _ = ordered_commits.append(commit) -%}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_descriptions(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized release notices and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_release_notices(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') -%}{% endmacro -%} diff --git a/config/release-templates/.components/macros.rst.j2 b/config/release-templates/.components/macros.rst.j2 index 779571c02..11c61d6de 100644 --- a/config/release-templates/.components/macros.rst.j2 +++ b/config/release-templates/.components/macros.rst.j2 @@ -1,35 +1,5 @@ -{# TODO: move to configuration for user to modify #} -{% set section_heading_translations = { - 'feat': 'features', - 'fix': 'bug fixes', - 'perf': 'performance improvements', - 'docs': 'documentation', - 'build': 'build system', - 'refactor': 'refactoring', - 'test': 'testing', - 'ci': 'continuous integration', - 'chore': 'chores', - 'style': 'code style', - } -%} - -{% set section_heading_order = section_heading_translations.values() %} +{% from 'macros.common.j2' import capitalize_first_letter_only %} -{% set emoji_map = { - 'breaking': 'πŸ’₯', - 'features': '✨', - 'bug fixes': 'πŸͺ²', - 'performance improvements': '⚑', - 'documentation': 'πŸ“–', - 'build system': 'βš™οΈ', - 'refactoring': '♻️', - 'testing': 'βœ…', - 'continuous integration': 'πŸ€–', - 'chores': '🧹', - 'code style': '🎨', - 'unknown': '❗', - 'release_note': 'πŸ’‘', -} %} {# MACRO: format a post-paragraph link reference in RST @@ -39,79 +9,18 @@ %} -{# - MACRO: Capitalize the first letter of a string only -#}{% macro capitalize_first_letter_only(sentence) -%}{{ (sentence[0] | upper) ~ sentence[1:] +{# MACRO: generate a heading underline that matches the exact length of the header #} +{% macro generate_heading_underline(header, underline_char) +%}{% set header_underline = [] +%}{% for _ in header +%}{% set __ = header_underline.append(underline_char) +%}{% endfor +%}{# # Print out the header underline +#}{{ header_underline | join }}{% endmacro %} -{# - MACRO: format commit summary line -#}{% macro format_commit_summary_line(commit) -%}{# # Check for Parsing Error -#}{% if commit.error is undefined -%}{# - # # Add any message links to the commit summary line -#}{% set summary_line = commit_msg_links(commit) -%}{# -#}{% if commit.scope -%}{% set summary_line = "**%s**: %s" | format(commit.scope, summary_line) -%}{% endif -%}{# - # # Return the modified summary_line -#}{{ summary_line -}}{# -#}{% else -%}{# # Return the first line of the commit if there was a Parsing Error -#}{{ (commit.commit.message | string).split("\n", maxsplit=1)[0] -}}{% endif -%}{% endmacro -%} - - -{# - MACRO: Create & return an non-inline RST link from a commit message - - Returns empty string if no PR/MR identifier is found -#}{% macro extract_pr_link_reference(commit) -%}{% if commit.error is undefined -%}{% set summary_line = commit.descriptions[0] -%}{# -#}{% if commit.linked_merge_request != "" -%}{# # Create a PR/MR reference url -#}{{ format_link_reference( - commit.linked_merge_request | pull_request_url, - "PR" ~ commit.linked_merge_request, - ) -}}{% endif -%}{% endif -%}{% endmacro -%} - -{# - MACRO: Extract issue references from a parsed commit object - - Stores the issue urls in the namespace object -#}{% macro extract_issue_link_references(ns, commit) -%}{% set issue_urls = [] -%}{# -#}{% if commit.linked_issues is defined and commit.linked_issues | length > 0 -%}{% for issue_num in commit.linked_issues -%}{# # Create an issue reference url -#}{% set _ = issue_urls.append( - format_link_reference( - issue_num | issue_url, - issue_num, - ) - ) -%}{% endfor -%}{% endif -%}{# - # # Store the issue urls in the namespace object -#}{% set ns.urls = issue_urls -%}{% endmacro -%} - {# MACRO: formats a commit message for a non-inline RST link for a commit hash and/or PR/MR #}{% macro commit_msg_links(commit) @@ -150,148 +59,68 @@ %} -{# MACRO: generate a heading underline that matches the exact length of the header #} -{% macro generate_heading_underline(header, underline_char) -%}{% set header_underline = [] -%}{% for _ in header -%}{% set __ = header_underline.append(underline_char) -%}{% endfor -%}{# # Print out the header underline -#}{{ header_underline | join -}}{% endmacro -%} - - {# - MACRO: format the breaking changes description by: - - Capitalizing the description - - Adding an optional scope prefix -#}{% macro format_breaking_changes_description(commit) -%}{% set ns = namespace(full_description="") -%}{# + MACRO: format commit summary line +#}{% macro format_commit_summary_line(commit) +%}{# # Check for Parsing Error #}{% if commit.error is undefined -%}{% for paragraph in commit.breaking_descriptions -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = capitalize_first_letter_only(paragraph) | trim | safe %}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim + # # Add any message links to the commit summary line +#}{% set summary_line = commit_msg_links(commit) %}{# #}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description - ) +%}{% set summary_line = "**%s**: %s" | format(commit.scope, summary_line) %}{% endif -%}{% endif %}{# -#}{{ ns.full_description -}}{% endmacro + # # Return the modified summary_line +#}{{ summary_line +}}{# +#}{% else +%}{# # Return the first line of the commit if there was a Parsing Error +#}{{ (commit.commit.message | string).split("\n", maxsplit=1)[0] +}}{% endif +%}{% endmacro %} {# - MACRO: format the release notice by: - - Capitalizing the description - - Adding an optional scope prefix -#}{% macro format_release_notice(commit) -%}{% set ns = namespace(full_description="") -%}{# -#}{% if commit.error is undefined -%}{% for paragraph in commit.release_notices -%}{% if paragraph | trim | length > 0 -%}{# -#}{% set paragraph_text = capitalize_first_letter_only(paragraph) | trim | safe -%}{# -#}{% set ns.full_description = [ - ns.full_description, - paragraph_text - ] | join("\n\n") -%}{# -#}{% endif -%}{% endfor -%}{# -#}{% set ns.full_description = ns.full_description | trim + MACRO: Extract issue references from a parsed commit object + - Stores the issue urls in the namespace object +#}{% macro extract_issue_link_references(ns, commit) +%}{% set issue_urls = [] %}{# -#}{% if commit.scope -%}{% set ns.full_description = "**%s**: %s" | format( - commit.scope, ns.full_description +#}{% if commit.linked_issues is defined and commit.linked_issues | length > 0 +%}{% for issue_num in commit.linked_issues +%}{# # Create an issue reference url +#}{% set _ = issue_urls.append( + format_link_reference( + issue_num | issue_url, + issue_num, + ) ) -%}{% endif +%}{% endfor %}{% endif %}{# -#}{{ ns.full_description -}}{% endmacro -%} - - -{# - MACRO: order commits alphabetically by scope and attribute - - Commits are sorted based on scope and then the attribute alphabetically - - Commits without scope are placed first and sorted alphabetically by the attribute - - parameter: ns (namespace) object with a commits list - - parameter: attr (string) attribute to sort by - - returns None but modifies the ns.commits list in place -#}{% macro order_commits_alphabetically_by_scope_and_attr(ns, attr) -%}{% set ordered_commits = [] -%}{# - # # Eliminate any ParseError commits from input set -#}{% set filtered_commits = ns.commits | rejectattr("error", "defined") | list -%}{# - # # grab all commits with no scope and sort alphabetically by attr -#}{% for commit in filtered_commits | rejectattr("scope") | sort(attribute=attr) -%}{% set _ = ordered_commits.append(commit) -%}{% endfor -%}{# - # # grab all commits with a scope and sort alphabetically by the scope and then attr -#}{% for commit in filtered_commits | selectattr("scope") | sort(attribute=(['scope', attr] | join(","))) -%}{% set _ = ordered_commits.append(commit) -%}{% endfor -%}{# - # # Return the ordered commits -#}{% set ns.commits = ordered_commits -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized summaries and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_descriptions(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'descriptions.0') -%}{% endmacro -%} - - -{# - MACRO: apply smart ordering of commits objects based on alphabetized breaking changes and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_brk_descriptions(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'breaking_descriptions.0') + # # Store the issue urls in the namespace object +#}{% set ns.urls = issue_urls %}{% endmacro %} {# - MACRO: apply smart ordering of commits objects based on alphabetized release notices and then scopes - - Commits are sorted based on the commit type and the commit message - - Commits are grouped by the commit type - - parameter: ns (namespace) object with a commits list - - returns None but modifies the ns.commits list in place -#}{% macro apply_alphabetical_ordering_by_release_notices(ns) -%}{% set _ = order_commits_alphabetically_by_scope_and_attr(ns, 'release_notices.0') + MACRO: Create & return an non-inline RST link from a commit message + - Returns empty string if no PR/MR identifier is found +#}{% macro extract_pr_link_reference(commit) +%}{% if commit.error is undefined +%}{% set summary_line = commit.descriptions[0] +%}{# +#}{% if commit.linked_merge_request != "" +%}{# # Create a PR/MR reference url +#}{{ format_link_reference( + commit.linked_merge_request | pull_request_url, + "PR" ~ commit.linked_merge_request, + ) +}}{% endif +%}{% endif %}{% endmacro %} From ce311adc300992642676cd27c58d39ce28513131 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Mon, 26 May 2025 14:01:11 -0600 Subject: [PATCH 120/120] chore: update in code todos for v10 (#1266) --- src/semantic_release/cli/changelog_writer.py | 2 +- src/semantic_release/cli/config.py | 10 +++++----- src/semantic_release/commit_parser/_base.py | 2 +- src/semantic_release/commit_parser/angular.py | 11 ----------- src/semantic_release/commit_parser/scipy.py | 4 ++-- src/semantic_release/commit_parser/token.py | 2 +- src/semantic_release/commit_parser/util.py | 2 +- .../commit_parser/test_conventional.py | 14 ++++++-------- .../semantic_release/commit_parser/test_emoji.py | 14 ++++++-------- .../semantic_release/commit_parser/test_scipy.py | 14 ++++++-------- 10 files changed, 29 insertions(+), 46 deletions(-) diff --git a/src/semantic_release/cli/changelog_writer.py b/src/semantic_release/cli/changelog_writer.py index 96020b73a..65e387896 100644 --- a/src/semantic_release/cli/changelog_writer.py +++ b/src/semantic_release/cli/changelog_writer.py @@ -269,7 +269,7 @@ def generate_release_notes( environment(autoescape=False, template_dir=tpl_dir) ) - # TODO: Remove in v10 + # TODO: Remove in v11 release_notes_env.globals["context"] = release_notes_env.globals["ctx"] = { "history": history, "mask_initial_release": mask_initial_release, diff --git a/src/semantic_release/cli/config.py b/src/semantic_release/cli/config.py index 59df69834..1d2057a48 100644 --- a/src/semantic_release/cli/config.py +++ b/src/semantic_release/cli/config.py @@ -148,7 +148,7 @@ def interpret_output_format(self) -> Self: class ChangelogConfig(BaseModel): - # TODO: BREAKING CHANGE v10, move to DefaultChangelogTemplatesConfig + # TODO: BREAKING CHANGE v11, move to DefaultChangelogTemplatesConfig changelog_file: str = "" """Deprecated! Moved to 'default_templates.changelog_file'""" @@ -191,7 +191,7 @@ def changelog_file_deprecation_warning(cls, val: str) -> str: @model_validator(mode="after") def move_changelog_file(self) -> Self: - # TODO: Remove this method in v10 + # TODO: Remove this method in v11 if not self.changelog_file: return self @@ -441,7 +441,7 @@ def set_default_opts(self) -> Self: parser_opts_type = None # If the commit parser is a known one, pull the default options object from it if self.commit_parser in _known_commit_parsers: - # TODO: BREAKING CHANGE v10 + # TODO: BREAKING CHANGE v11 # parser_opts_type = ( # _known_commit_parsers[self.commit_parser] # .get_default_options() @@ -454,7 +454,7 @@ def set_default_opts(self) -> Self: try: # if its a custom parser, try to import it and pull the default options object type custom_class = dynamic_import(self.commit_parser) - # TODO: BREAKING CHANGE v10 + # TODO: BREAKING CHANGE v11 # parser_opts_type = custom_class.get_default_options().__class__ if hasattr(custom_class, "parser_options"): parser_opts_type = custom_class.parser_options @@ -695,7 +695,7 @@ def from_raw_config( # noqa: C901 ) from err commit_parser_opts_class = commit_parser_cls.parser_options - # TODO: Breaking change v10 + # TODO: Breaking change v11 # commit_parser_opts_class = commit_parser_cls.get_default_options().__class__ try: commit_parser = commit_parser_cls( diff --git a/src/semantic_release/commit_parser/_base.py b/src/semantic_release/commit_parser/_base.py index 04d2f56bd..a144e0945 100644 --- a/src/semantic_release/commit_parser/_base.py +++ b/src/semantic_release/commit_parser/_base.py @@ -74,7 +74,7 @@ def __init__(self, options: _OPTS | None = None) -> None: options if options is not None else self.get_default_options() ) - # TODO: BREAKING CHANGE v10, add abstract method for all custom parsers + # TODO: BREAKING CHANGE v11, add abstract method for all custom parsers # @staticmethod # @abstractmethod def get_default_options(self) -> _OPTS: diff --git a/src/semantic_release/commit_parser/angular.py b/src/semantic_release/commit_parser/angular.py index eeef82796..411ac844b 100644 --- a/src/semantic_release/commit_parser/angular.py +++ b/src/semantic_release/commit_parser/angular.py @@ -94,11 +94,9 @@ class AngularParserOptions(ParserOptions): default_bump_level: LevelBump = LevelBump.NO_RELEASE """The minimum bump level to apply to valid commit message.""" - # TODO: breaking change v10, change default to True parse_squash_commits: bool = False """Toggle flag for whether or not to parse squash commits""" - # TODO: breaking change v10, change default to True ignore_merge_commits: bool = False """Toggle flag for whether or not to ignore merge commits""" @@ -236,15 +234,11 @@ def commit_body_components_separator( ) -> dict[str, list[str]]: if (match := breaking_re.match(text)) and (brk_desc := match.group(1)): accumulator["breaking_descriptions"].append(brk_desc) - # TODO: breaking change v10, removes breaking change footers from descriptions - # return accumulator elif (match := self.notice_selector.match(text)) and ( notice := match.group("notice") ): accumulator["notices"].append(notice) - # TODO: breaking change v10, removes notice footers from descriptions - # return accumulator elif match := self.issue_selector.search(text): # if match := self.issue_selector.search(text): @@ -265,8 +259,6 @@ def commit_body_components_separator( accumulator["linked_issues"] = sort_numerically( set(accumulator["linked_issues"]).union(new_issue_refs) ) - # TODO: breaking change v10, removes resolution footers from descriptions - # return accumulator # Prevent appending duplicate descriptions if text not in accumulator["descriptions"]: @@ -287,9 +279,6 @@ def parse_message(self, message: str) -> ParsedMessageResult | None: linked_merge_request = "" if mr_match := self.mr_selector.search(parsed_subject): linked_merge_request = mr_match.group("mr_number") - # TODO: breaking change v10, removes PR number from subject/descriptions - # expects changelog template to format the line accordingly - # parsed_subject = self.pr_selector.sub("", parsed_subject).strip() body_components: dict[str, list[str]] = reduce( self.commit_body_components_separator, diff --git a/src/semantic_release/commit_parser/scipy.py b/src/semantic_release/commit_parser/scipy.py index 7e0e6b246..e6988ea83 100644 --- a/src/semantic_release/commit_parser/scipy.py +++ b/src/semantic_release/commit_parser/scipy.py @@ -145,7 +145,7 @@ class ScipyParserOptions(ParserOptions): one of these prefixes, it will not be considered a valid commit message. """ - # TODO: breaking v10, make consistent with AngularParserOptions + # TODO: breaking v11, make consistent with AngularParserOptions default_level_bump: LevelBump = LevelBump.NO_RELEASE """The minimum bump level to apply to valid commit message.""" @@ -161,7 +161,7 @@ def tag_to_level(self) -> dict[str, LevelBump]: return self._tag_to_level def __post_init__(self) -> None: - # TODO: breaking v10, remove as the name is now consistent + # TODO: breaking v11, remove as the name is now consistent self.default_bump_level = self.default_level_bump self._tag_to_level: dict[str, LevelBump] = { str(tag): level diff --git a/src/semantic_release/commit_parser/token.py b/src/semantic_release/commit_parser/token.py index a9bb254de..332f4283b 100644 --- a/src/semantic_release/commit_parser/token.py +++ b/src/semantic_release/commit_parser/token.py @@ -150,7 +150,7 @@ def from_parsed_message_result( """A convience method to create a ParsedCommit object from a ParsedMessageResult object and a Commit object.""" return ParsedCommit( bump=parsed_message_result.bump, - # TODO: breaking v10, swap back to type rather than category + # TODO: breaking v11, swap back to type rather than category type=parsed_message_result.category, scope=parsed_message_result.scope, descriptions=list(parsed_message_result.descriptions), diff --git a/src/semantic_release/commit_parser/util.py b/src/semantic_release/commit_parser/util.py index 9c1322b41..258e8224b 100644 --- a/src/semantic_release/commit_parser/util.py +++ b/src/semantic_release/commit_parser/util.py @@ -6,7 +6,7 @@ from re import MULTILINE, compile as regexp from typing import TYPE_CHECKING -# TODO: remove in v10 +# TODO: remove in v11 from semantic_release.helpers import ( sort_numerically, # noqa: F401 # TODO: maintained for compatibility ) diff --git a/tests/unit/semantic_release/commit_parser/test_conventional.py b/tests/unit/semantic_release/commit_parser/test_conventional.py index 02cd4f5de..9298c1f24 100644 --- a/tests/unit/semantic_release/commit_parser/test_conventional.py +++ b/tests/unit/semantic_release/commit_parser/test_conventional.py @@ -205,7 +205,7 @@ def test_parser_squashed_commit_bitbucket_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -388,7 +388,7 @@ def test_parser_squashed_commit_git_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -549,7 +549,7 @@ def test_parser_squashed_commit_github_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -699,7 +699,6 @@ def test_parser_return_subject_from_commit_message( @pytest.mark.parametrize( "message, subject, merge_request_number", - # TODO: in v10, we will remove the merge request number from the subject line [ # GitHub, Gitea style ( @@ -1109,10 +1108,9 @@ def test_parser_return_release_notices_from_commit_message( assert isinstance(result, ParsedCommit) assert tuple(notices) == result.release_notices - # TODO: v10, remove this - # full_description = str.join("\n\n", result.descriptions) - # full_notice = str.join("\n\n", result.release_notices) - # assert full_notice not in full_description + full_description = str.join("\n\n", result.descriptions) + full_notice = str.join("\n\n", result.release_notices) + assert full_notice not in full_description ############################## diff --git a/tests/unit/semantic_release/commit_parser/test_emoji.py b/tests/unit/semantic_release/commit_parser/test_emoji.py index ac7708ebb..c477579ec 100644 --- a/tests/unit/semantic_release/commit_parser/test_emoji.py +++ b/tests/unit/semantic_release/commit_parser/test_emoji.py @@ -136,7 +136,6 @@ def test_parser_return_linked_merge_request_from_commit_message( @pytest.mark.parametrize( "message, linked_issues", - # TODO: in v10, we will remove the issue reference footers from the descriptions [ *[ # GitHub, Gitea, GitLab style @@ -510,10 +509,9 @@ def test_parser_return_release_notices_from_commit_message( assert isinstance(result, ParsedCommit) assert tuple(notices) == result.release_notices - # TODO: v10, remove this - # full_description = str.join("\n\n", result.descriptions) - # full_notice = str.join("\n\n", result.release_notices) - # assert full_notice not in full_description + full_description = str.join("\n\n", result.descriptions) + full_notice = str.join("\n\n", result.release_notices) + assert full_notice not in full_description @pytest.mark.parametrize( @@ -689,7 +687,7 @@ def test_parser_squashed_commit_bitbucket_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -878,7 +876,7 @@ def test_parser_squashed_commit_git_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -1042,7 +1040,7 @@ def test_parser_squashed_commit_github_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues diff --git a/tests/unit/semantic_release/commit_parser/test_scipy.py b/tests/unit/semantic_release/commit_parser/test_scipy.py index 46f70b211..ce714c0bc 100644 --- a/tests/unit/semantic_release/commit_parser/test_scipy.py +++ b/tests/unit/semantic_release/commit_parser/test_scipy.py @@ -615,7 +615,7 @@ def test_parser_squashed_commit_bitbucket_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -798,7 +798,7 @@ def test_parser_squashed_commit_git_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -959,7 +959,7 @@ def test_parser_squashed_commit_github_squash_style( assert expected["type"] == result.type # Optional assert expected.get("scope", "") == result.scope - # TODO: v10 change to tuples + # TODO: v11 change to tuples assert expected.get("descriptions", []) == result.descriptions assert expected.get("breaking_descriptions", []) == result.breaking_descriptions assert expected.get("linked_issues", ()) == result.linked_issues @@ -968,7 +968,6 @@ def test_parser_squashed_commit_github_squash_style( @pytest.mark.parametrize( "message, linked_issues", - # TODO: in v10, we will remove the issue reference footers from the descriptions [ *[ # GitHub, Gitea, GitLab style @@ -1331,10 +1330,9 @@ def test_parser_return_release_notices_from_commit_message( assert isinstance(result, ParsedCommit) assert tuple(notices) == result.release_notices - # TODO: v10, remove this - # full_description = str.join("\n\n", result.descriptions) - # full_notice = str.join("\n\n", result.release_notices) - # assert full_notice not in full_description + full_description = str.join("\n\n", result.descriptions) + full_notice = str.join("\n\n", result.release_notices) + assert full_notice not in full_description def test_parser_ignore_merge_commit(