Skip to content

Commit a5b92a1

Browse files
committed
docs(customization): verify executable no-bump plugin example
1 parent f565e81 commit a5b92a1

5 files changed

Lines changed: 158 additions & 12 deletions

File tree

‎docs/customization/python_class.md‎

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,25 @@ You need to define 2 parameters inside your custom `BaseCommitizen`.
101101
| `bump_pattern` | `str` | `None` | Regex to extract information from commit (subject and body) |
102102
| `bump_map` | `dict` | `None` | Dictionary mapping the extracted information to a `SemVer` increment type (`MAJOR`, `MINOR`, `PATCH`). Use `None` when a matched rule should not bump the version. |
103103

104-
Let's see an example.
105-
106-
```python title="cz_strange.py"
107-
from commitizen.cz.base import BaseCommitizen
108-
109-
110-
class StrangeCommitizen(BaseCommitizen):
111-
bump_pattern = r"^(break|new|fix|hotfix)"
112-
bump_map = {"break": "MAJOR", "new": "MINOR", "fix": "PATCH", "hotfix": "PATCH"}
104+
If you only need to customize bump behavior, subclassing an existing rule set
105+
keeps the example executable while still overriding the bump rules. The module
106+
below is the same file exercised by Commitizen's test suite and type-checked by
107+
mypy.
108+
109+
<!-- blacken-docs:off -->
110+
```python title="cz_docs_only.py"
111+
--8<-- "docs/examples/cz_docs_only.py"
113112
```
113+
<!-- blacken-docs:on -->
114114

115-
That's it, your Commitizen now supports custom rules, and you can run.
115+
Package and install `cz_docs_only.py` just like the earlier `cz_jira.py`
116+
example, and expose it through the same `commitizen.plugin` entry point group,
117+
for example with `cz_docs_only = cz_docs_only:DocsOnlyPatchCommitizen`.
118+
After installing that package, your Commitizen now supports custom rules, and
119+
you can run:
116120

117121
```bash
118-
cz -n cz_strange bump
122+
cz -n cz_docs_only bump
119123
```
120124

121125
### Filter commits before bump and changelog generation

‎docs/examples/cz_docs_only.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Executable custom bump-rule example published in the documentation."""
2+
3+
from __future__ import annotations
4+
5+
from commitizen.cz.conventional_commits import ConventionalCommitsCz
6+
7+
8+
class DocsOnlyPatchCommitizen(ConventionalCommitsCz):
9+
"""Skip version bumps for docs commits while patch-bumping fixes.
10+
11+
Example:
12+
Configure the plugin as ``cz_docs_only`` to ignore ``docs:`` commits for
13+
version bumps while still treating ``fix:`` commits as patch releases.
14+
15+
Attributes:
16+
bump_pattern: Extracts the commit types that participate in bump logic.
17+
bump_map: Maps ``docs`` to no increment and ``fix`` to a patch bump.
18+
"""
19+
20+
bump_pattern = r"^(docs|fix)(?:\([^()\r\n]*\))?:"
21+
bump_map = {"docs": None, "fix": "PATCH"}

‎mkdocs.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ markdown_extensions:
101101
- codehilite
102102
- extra
103103
- pymdownx.highlight
104+
- pymdownx.snippets
104105
- pymdownx.superfences
105106
- toc:
106107
permalink: true

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ known-first-party = ["commitizen", "tests"]
253253
convention = "google"
254254

255255
[tool.mypy]
256-
files = ["commitizen", "tests", "scripts"]
256+
files = ["commitizen", "tests", "scripts", "docs/examples"]
257257
disallow_untyped_decorators = true
258258
disallow_subclassing_any = true
259259
warn_return_any = true
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""Tests for executable custom-plugin examples published in the docs."""
2+
3+
from __future__ import annotations
4+
5+
import importlib.util
6+
from pathlib import Path
7+
from typing import TYPE_CHECKING, cast
8+
9+
import pytest
10+
11+
from commitizen import git
12+
from commitizen.cz import registry
13+
from commitizen.exceptions import NoneIncrementExit
14+
15+
if TYPE_CHECKING:
16+
from pytest_mock import MockFixture
17+
18+
from commitizen.cz.base import BaseCommitizen
19+
from tests.utils import UtilFixture
20+
21+
22+
DOCUMENTED_PLUGIN_PATH = (
23+
Path(__file__).resolve().parents[2] / "docs" / "examples" / "cz_docs_only.py"
24+
)
25+
26+
27+
def _load_documented_plugin() -> type[BaseCommitizen]:
28+
"""Load the custom plugin example from the file embedded in the docs."""
29+
spec = importlib.util.spec_from_file_location(
30+
"tests_documented_cz_docs_only", DOCUMENTED_PLUGIN_PATH
31+
)
32+
assert spec is not None
33+
assert spec.loader is not None
34+
35+
module = importlib.util.module_from_spec(spec)
36+
spec.loader.exec_module(module)
37+
38+
return cast("type[BaseCommitizen]", getattr(module, "DocsOnlyPatchCommitizen"))
39+
40+
41+
@pytest.fixture
42+
def documented_plugin_name(mocker: MockFixture) -> str:
43+
"""Expose the documented example through Commitizen's plugin registry."""
44+
plugin_name = "cz_docs_only"
45+
plugin_class = _load_documented_plugin()
46+
mocker.patch.dict("commitizen.cz.registry", {**registry, plugin_name: plugin_class})
47+
return plugin_name
48+
49+
50+
@pytest.mark.parametrize("major_version_zero", [False, True])
51+
@pytest.mark.parametrize(
52+
"commit_message",
53+
["fix: ship executable docs example", "fix(api): ship executable docs example"],
54+
)
55+
@pytest.mark.usefixtures("tmp_commitizen_project")
56+
def test_documented_python_plugin_fix_commit_bumps_patch(
57+
util: UtilFixture,
58+
documented_plugin_name: str,
59+
major_version_zero: bool,
60+
commit_message: str,
61+
) -> None:
62+
"""The documented example bumps a fix commit as a patch release."""
63+
util.create_file_and_commit(commit_message)
64+
65+
args = ["--name", documented_plugin_name, "bump", "--yes"]
66+
if major_version_zero:
67+
args.append("--major-version-zero")
68+
util.run_cli(*args)
69+
70+
assert git.tag_exist("0.1.1") is True
71+
72+
73+
@pytest.mark.parametrize("major_version_zero", [False, True])
74+
@pytest.mark.parametrize(
75+
"commit_message",
76+
["docs: expand plugin guide", "docs(api): expand plugin guide"],
77+
)
78+
@pytest.mark.usefixtures("tmp_commitizen_project")
79+
def test_documented_python_plugin_docs_commit_does_not_bump(
80+
util: UtilFixture,
81+
documented_plugin_name: str,
82+
major_version_zero: bool,
83+
commit_message: str,
84+
) -> None:
85+
"""The documented example treats docs commits as a no-bump match."""
86+
first_bump_args = ["--name", documented_plugin_name, "bump", "--yes"]
87+
if major_version_zero:
88+
first_bump_args.append("--major-version-zero")
89+
90+
util.create_file_and_commit("fix: seed release")
91+
util.run_cli(*first_bump_args)
92+
util.create_file_and_commit(commit_message)
93+
94+
with pytest.raises(NoneIncrementExit):
95+
util.run_cli(*first_bump_args)
96+
97+
assert git.tag_exist("0.1.2") is False
98+
99+
100+
@pytest.mark.parametrize(
101+
"commit_message",
102+
[
103+
"fix!: drop old API",
104+
"fixup! rebase cleanup",
105+
"fixture: rename helper",
106+
],
107+
)
108+
@pytest.mark.usefixtures("tmp_commitizen_project")
109+
def test_documented_python_plugin_ignores_nonmatching_fix_prefixes(
110+
util: UtilFixture, documented_plugin_name: str, commit_message: str
111+
) -> None:
112+
"""Only plain fix commits with an optional scope participate in bumping."""
113+
util.create_file_and_commit("fix: seed release")
114+
util.run_cli("--name", documented_plugin_name, "bump", "--yes")
115+
util.create_file_and_commit(commit_message)
116+
117+
with pytest.raises(NoneIncrementExit):
118+
util.run_cli("--name", documented_plugin_name, "bump", "--yes")
119+
120+
assert git.tag_exist("0.1.2") is False

0 commit comments

Comments
 (0)