Skip to content

Commit 4b1fd67

Browse files
feat: commit message check can be opted out
- no (or an empty) [commit.types] in afterpython.toml means no check, instead of blocking every commit - ap init asks whether to check commit messages; if not, [commit.types] is written commented out Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 4ad2cdf commit 4b1fd67

5 files changed

Lines changed: 56 additions & 15 deletions

File tree

‎afterpython/doc/package_maintenance/commit_workflow.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ wip = "Work in progress"
4545
```
4646
To allow a new type, add a line, e.g. `deps = "Dependency updates"`; to disallow one, remove its line. The descriptions are shown in the commit message template and in the error message when a commit is rejected.
4747

48+
:::{note} Opting out
49+
Commit messages are only checked when `[commit.types]` has at least one type.
50+
To opt out, remove the section or leave it empty. If you opted out during `ap init`, the section is commented out, uncomment it to opt in.
51+
:::
52+
4853
### Bypassing Checks
4954
- `SKIP=ap-commit git commit` — skip the commit message check
5055
- `git commit --no-verify` — skip all pre-commit and commit message hooks

‎src/afterpython/cli/commands/commit_msg.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ def _subject(message: str) -> str:
6767
)
6868
@click.pass_context
6969
def commit_msg(ctx, message_file: Path, template: bool):
70-
"""Check a commit message against [commit.types] in afterpython.toml (used by the ap-commit hook)"""
70+
"""Check a commit message against [commit.types] in afterpython.toml (used by the ap-commit hook)
71+
72+
Without (or with an empty) [commit.types], any message is accepted.
73+
"""
7174
from afterpython._io.toml import _from_tomlkit
7275
from afterpython.tools._afterpython import read_afterpython
7376

@@ -81,12 +84,9 @@ def commit_msg(ctx, message_file: Path, template: bool):
8184
_add_template(message_file, commit_types)
8285
return
8386

87+
# opted out: no (or an empty) [commit.types] section means no check
8488
if not commit_types:
85-
click.echo(
86-
"❌ No commit types found, add a [commit.types] section to afterpython/afterpython.toml",
87-
err=True,
88-
)
89-
ctx.exit(1)
89+
return
9090

9191
subject = _subject(message_file.read_text(encoding="utf-8"))
9292
# empty messages are rejected by git itself

‎src/afterpython/cli/commands/init.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,17 @@ def init(ctx, yes, skip_website: bool):
142142

143143
init_pyproject()
144144

145-
init_afterpython()
145+
# only asked when afterpython.toml is created, it's ignored otherwise
146+
commit_types = (
147+
yes
148+
or (afterpython_path / "afterpython.toml").exists()
149+
or click.confirm(
150+
"\nCheck commit messages against conventional commit types "
151+
"(e.g. 'feat: add X', types listed in afterpython.toml)?",
152+
default=True,
153+
)
154+
)
155+
init_afterpython(commit_types=commit_types)
146156

147157
if not skip_website:
148158
# check=True so a failure inside `ap init website` aborts the parent

‎src/afterpython/tools/_afterpython.py‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,37 @@ def update_afterpython(data_update: dict):
106106
write_toml(afterpython_toml_path, existing_data)
107107

108108

109-
def init_afterpython():
110-
"""Initialize afterpython.toml"""
109+
def _comment_out_commit_types(text: str) -> str:
110+
"""Comment out the [commit.types] section, so the ap-commit hook doesn't check commit messages"""
111+
lines = text.splitlines()
112+
start = lines.index("[commit.types]")
113+
end = next(
114+
(i for i in range(start + 1, len(lines)) if lines[i].startswith("[")),
115+
len(lines),
116+
)
117+
lines[start:end] = [
118+
"# uncomment to check commit messages against these types",
119+
*(f"# {line}" if line.strip() else line for line in lines[start:end]),
120+
]
121+
return "\n".join(lines) + "\n"
122+
123+
124+
def init_afterpython(commit_types: bool = True):
125+
"""Initialize afterpython.toml
126+
127+
Args:
128+
commit_types: if False, [commit.types] is commented out (commit messages are not checked)
129+
"""
111130
afterpython_toml_path = ap.paths.afterpython_path / "afterpython.toml"
112131
if afterpython_toml_path.exists():
113132
print(f"afterpython.toml already exists at {afterpython_toml_path}")
114133
return
115134
afterpython_template_path = ap.paths.templates_path / "afterpython-template.toml"
116-
shutil.copy(afterpython_template_path, afterpython_toml_path)
135+
if commit_types:
136+
shutil.copy(afterpython_template_path, afterpython_toml_path)
137+
else:
138+
text = afterpython_template_path.read_text(encoding="utf-8")
139+
afterpython_toml_path.write_text(
140+
_comment_out_commit_types(text), encoding="utf-8"
141+
)
117142
print(f"Created {afterpython_toml_path}")

‎tests/test_commit_msg.py‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,17 @@ def test_error_lists_allowed_types(tmp_path):
7676
assert t in result.output and desc in result.output
7777

7878

79-
def test_missing_commit_types(tmp_path, monkeypatch):
79+
@pytest.mark.parametrize("toml", ["", "[commit.types]\n"])
80+
def test_no_commit_types_skips_check(tmp_path, monkeypatch, toml):
8081
import tomlkit
8182

8283
monkeypatch.setattr(
8384
"afterpython.tools._afterpython.read_afterpython",
84-
lambda: tomlkit.document(),
85+
lambda: tomlkit.parse(toml),
8586
)
86-
result = run(tmp_path, "feat: add X")
87-
assert result.exit_code == 1
88-
assert "[commit.types]" in result.output
87+
result = run(tmp_path, "anything goes")
88+
assert result.exit_code == 0, result.output
89+
assert result.output == ""
8990

9091

9192
GIT_DEFAULT_MESSAGE = (

0 commit comments

Comments
 (0)