Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ jobs:
- name: Build | Action Container
id: container-builder
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: ${{ env.ACTION_SRC_DIR }}
load: true # add to `docker images`
Expand Down
50 changes: 50 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@
CHANGELOG
=========

.. _changelog-v10.7.0:

v10.7.0 (2026-09-22)
====================

✨ Features
-----------

* **cmd-stamp**: Expand version stamp mechanism to support typed python variables, closes `#1443`_
(`PR#1485`_, `947c57b`_)

* **cmd-version**: Add optional ``git --signoff`` to version commits, closes `#1441`_ (`PR#1492`_,
`3cecbeb`_)

📖 Documentation
----------------

* **configuration**: Added ``signoff_commit`` setting definition (`PR#1492`_, `3cecbeb`_)

* **configuration**: Document Python type annotation support for ``version_variables`` (`PR#1485`_,
`947c57b`_)

⚙️ Build System
----------------

* **deps**: Correct ``click`` range specification for python3.10+ (`PR#1486`_, `e4b3bad`_)

* **deps**: Expand ``click`` dependency range to include ``v8.5.*`` (`PR#1489`_, `fa87c95`_)

💡 Additional Release Information
---------------------------------

* **cmd-version**: With this release, we added a boolean flag, ``semantic_release.signoff_commit``,
as an available configuration option to toggle the addition of the ``Signed-off-by`` git
trailer/footer message to release commits made by Python Semantic Release. Default is currently
set to ``False`` but if you want to try this option, set this configuration setting to ``True``.
In a future major release, this setting will be set to ``True`` by default.

.. _#1441: https://github.com/python-semantic-release/python-semantic-release/issues/1441
.. _#1443: https://github.com/python-semantic-release/python-semantic-release/issues/1443
.. _3cecbeb: https://github.com/python-semantic-release/python-semantic-release/commit/3cecbebb9a87ff6adf46e9334f18bd9c84655608
.. _947c57b: https://github.com/python-semantic-release/python-semantic-release/commit/947c57b48bee85753cf40296b98529da26bf4b58
.. _e4b3bad: https://github.com/python-semantic-release/python-semantic-release/commit/e4b3badb127b751d4946ccd7efb3ec0e8c159895
.. _fa87c95: https://github.com/python-semantic-release/python-semantic-release/commit/fa87c95ffbdaae3b7516dcc355e57bd5f4632c1f
.. _PR#1485: https://github.com/python-semantic-release/python-semantic-release/pull/1485
.. _PR#1486: https://github.com/python-semantic-release/python-semantic-release/pull/1486
.. _PR#1489: https://github.com/python-semantic-release/python-semantic-release/pull/1489
.. _PR#1492: https://github.com/python-semantic-release/python-semantic-release/pull/1492


.. _changelog-v10.6.2:

v10.6.2 (2026-08-28)
Expand Down
4 changes: 2 additions & 2 deletions docs/configuration/automatic-releases/github-actions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -893,14 +893,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@COMMIT_HASH # v10.6.2
uses: python-semantic-release/python-semantic-release@COMMIT_HASH # v10.7.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@COMMIT_HASH # v10.6.2
uses: python-semantic-release/publish-action@COMMIT_HASH # v10.7.0
if: steps.release.outputs.released == 'true'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
26 changes: 23 additions & 3 deletions docs/configuration/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,26 @@ raise an error and exit.

----

.. _config-signoff_commit:

``signoff_commit``
"""""""""""""""""""

**Type:** ``bool``

When true, Python Semantic Release will add a ``Signed-off-by`` trailer to the version
commit it creates during :ref:`cmd-version`, using the configured :ref:`config-commit_author`
name and email. This is equivalent to running ``git commit --signoff`` and is commonly used
to comply with a `Developer Certificate of Origin`_ (DCO) requirement.

.. _Developer Certificate of Origin: https://developercertificate.org/

This option has no effect when the ``--no-commit`` option is passed.

**Default:** ``false``

----

.. _config-tag_format:

``tag_format``
Expand Down Expand Up @@ -1377,7 +1397,7 @@ version numbers.
]

First, the ``__version__`` variable in ``src/semantic_release/__init__.py`` will be updated
with the next version using the `SemVer`_ number format. As of $NEW_RELEASE_TAG, this works
with the next version using the `SemVer`_ number format. As of v10.7.0, this works
both with and without a Python type annotation on the variable:

.. code-block:: diff
Expand All @@ -1387,7 +1407,7 @@ both with and without a Python type annotation on the variable:
- __version__ = "0.1.0"
+ __version__ = "0.2.0"

# or with a type annotation ($NEW_RELEASE_TAG or greater):
# or with a type annotation (v10.7.0 or greater):
- __version__: str = "0.1.0"
+ __version__: str = "0.2.0"

Expand Down Expand Up @@ -1455,7 +1475,7 @@ The regular expression generated from the ``version_variables`` definition will:
the symbol. As of v10.0.0, a double-equals (``==``) operator is also supported
as a valid operand symbol. As of v10.5.0, PSR can omit all operands as long
as there is at least one whitespace character between the variable name and the version.
As of $NEW_RELEASE_TAG, an optional Python-style type annotation (e.g. ``: str``,
As of v10.7.0, an optional Python-style type annotation (e.g. ``: str``,
``: typing.Final[str]``) between the variable name and the assignment operator is
also supported.

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "python-semantic-release"
version = "10.6.2"
version = "10.7.0"
description = "Automatic Semantic Versioning for Python projects"
requires-python = "~= 3.8"
license = { text = "MIT" }
Expand Down Expand Up @@ -74,7 +74,7 @@ docs = [
]
test = [
"coverage[toml] ~= 7.0",
"filelock ~= 3.15",
"filelock >= 3.15, < 5.0",
"cj365-flatdict ~= 5.0",
"freezegun ~= 1.5",
"pyyaml ~= 6.0",
Expand Down
2 changes: 1 addition & 1 deletion src/gh_action/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python-semantic-release == 10.6.2
python-semantic-release == 10.7.0
2 changes: 2 additions & 0 deletions src/semantic_release/cli/commands/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ def version( # noqa: C901
commit_message = runtime.commit_message
major_on_zero = runtime.major_on_zero
no_verify = runtime.no_git_verify
signoff_commit = runtime.signoff_commit
opts = runtime.global_cli_options
add_partial_tags = config.add_partial_tags
gha_output = VersionGitHubActionsOutput(
Expand Down Expand Up @@ -713,6 +714,7 @@ def version( # noqa: C901
message=commit_message.format(version=new_version),
date=int(commit_date.timestamp()),
no_verify=no_verify,
signoff=signoff_commit,
noop=opts.noop,
)
except GitCommitEmptyIndexError:
Expand Down
3 changes: 3 additions & 0 deletions src/semantic_release/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ class RawConfig(BaseModel):
repo_dir: Path = Field(default=cast("Path", "."), validate_default=True)
remote: RemoteConfig = RemoteConfig()
no_git_verify: bool = False
signoff_commit: bool = False
tag_format: str = "v{version}"
add_partial_tags: bool = False
publish: PublishConfig = PublishConfig()
Expand Down Expand Up @@ -635,6 +636,7 @@ class RuntimeContext:
allow_zero_version: bool
prerelease: bool
no_git_verify: bool
signoff_commit: bool
assets: List[str]
commit_author: Actor
commit_message: str
Expand Down Expand Up @@ -986,6 +988,7 @@ def from_raw_config( # noqa: C901
global_cli_options=global_cli_options,
masker=masker,
no_git_verify=raw.no_git_verify,
signoff_commit=raw.signoff_commit,
)
# credential masker
self.apply_log_masking(self.masker)
Expand Down
3 changes: 3 additions & 0 deletions src/semantic_release/gitproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def git_commit(
date: int | None = None,
commit_all: bool = False,
no_verify: bool = False,
signoff: bool = False,
noop: bool = False,
) -> None:
git_args = dict(
Expand All @@ -184,6 +185,7 @@ def git_commit(
"m": message,
"date": date,
"no_verify": no_verify,
"signoff": signoff,
}.items(),
)
)
Expand All @@ -209,6 +211,7 @@ def git_commit(
command += f"git commit -m '{indented_commit_message}'"
command += "--all" if commit_all else ""
command += "--no-verify" if no_verify else ""
command += "--signoff" if signoff else ""

noop_report(
indented(
Expand Down
109 changes: 109 additions & 0 deletions tests/e2e/cmd_version/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,115 @@ def test_version_no_git_verify(
assert post_mocker.call_count == 1 # vcs release creation occurred


@pytest.mark.parametrize(
"repo_result",
[lazy_fixture(repo_w_trunk_only_conventional_commits.__name__)],
)
def test_version_signoff_commit(
repo_result: BuiltRepoResult,
run_cli: RunCliFn,
update_pyproject_toml: UpdatePyprojectTomlFn,
mocked_git_fetch: MagicMock,
mocked_git_push: MagicMock,
post_mocker: Mocker,
):
"""
Given signoff_commit is enabled, when a version commit is made, then the commit
message includes a Signed-off-by trailer matching the configured commit author.
"""
repo = repo_result["repo"]

# setup: set configuration setting
update_pyproject_toml("tool.semantic_release.signoff_commit", True)
repo.git.commit(
m="chore: adjust project configuration for signoff release commits", a=True
)
# Fake an automated push to remote by updating the remote tracking branch
repo.git.update_ref(
f"refs/remotes/origin/{repo.active_branch.name}",
repo.head.commit.hexsha,
)

# Take measurement beforehand
head_sha_before = repo.head.commit.hexsha
tags_before = {tag.name for tag in repo.tags}

# Execute
cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--patch"]
result = run_cli(cli_cmd[1:])

# Take measurement after the command
head_after = repo.head.commit
tags_after = {tag.name for tag in repo.tags}
tags_set_difference = set.difference(tags_after, tags_before)
commit_author = head_after.author
expected_signed_off_footer = (
f"Signed-off-by: {commit_author.name} <{commit_author.email}>"
)

# Evaluate (normal release actions should have occurred when forced patch bump)
assert_successful_exit_code(result, cli_cmd)
assert [head_sha_before] == [head.hexsha for head in head_after.parents]
assert len(tags_set_difference) == 1 # A tag has been created
assert expected_signed_off_footer in str(head_after.message)
assert mocked_git_fetch.call_count == 1 # fetch called to check for remote changes
assert mocked_git_push.call_count == 2 # 1 for commit, 1 for tag
assert post_mocker.call_count == 1 # vcs release creation occurred


@pytest.mark.parametrize(
"repo_result",
[lazy_fixture(repo_w_trunk_only_conventional_commits.__name__)],
)
def test_version_signoff_commit_disabled(
repo_result: BuiltRepoResult,
run_cli: RunCliFn,
update_pyproject_toml: UpdatePyprojectTomlFn,
mocked_git_fetch: MagicMock,
mocked_git_push: MagicMock,
post_mocker: Mocker,
):
"""
Given signoff_commit is disabled (the default), when a version commit is made,
then the commit message does not include a Signed-off-by trailer.
"""
repo = repo_result["repo"]

# setup: explicitly set configuration setting to False
update_pyproject_toml("tool.semantic_release.signoff_commit", False)
repo.git.commit(
m="chore: adjust project configuration for non-signoff release commits",
a=True,
)
# Fake an automated push to remote by updating the remote tracking branch
repo.git.update_ref(
f"refs/remotes/origin/{repo.active_branch.name}",
repo.head.commit.hexsha,
)

# Take measurement beforehand
head_sha_before = repo.head.commit.hexsha
tags_before = {tag.name for tag in repo.tags}

# Execute
cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--patch"]
result = run_cli(cli_cmd[1:])

# Take measurement after the command
head_after = repo.head.commit
tags_after = {tag.name for tag in repo.tags}
tags_set_difference = set.difference(tags_after, tags_before)

# Evaluate (normal release actions should have occurred when forced patch bump)
assert_successful_exit_code(result, cli_cmd)
assert [head_sha_before] == [head.hexsha for head in head_after.parents]
assert len(tags_set_difference) == 1 # A tag has been created
assert "Signed-off-by:" not in str(head_after.message)
assert mocked_git_fetch.call_count == 1 # fetch called to check for remote changes
assert mocked_git_push.call_count == 2 # 1 for commit, 1 for tag
assert post_mocker.call_count == 1 # vcs release creation occurred


@pytest.mark.parametrize(
"repo_result", [lazy_fixture(repo_w_trunk_only_conventional_commits.__name__)]
)
Expand Down
Loading