Commit classification¶
What GenerateFromRepo does to each commit message: which types it recognises, which
category each becomes, and which commits never reach the output.
Only the subject line — the first line of the commit message — is classified. The body
is read for one thing only: a BREAKING CHANGE: footer.
Recognised commit types¶
Parsing uses leodido/go-conventionalcommits
with its conventional type set, which is a closed list of eleven types:
build, chore, ci, docs, feat, fix, perf, refactor, revert, style,
test
A subject whose type is not on that list is not a Conventional Commit as far as this
module is concerned, however well-formed it looks. security: patch CVE-2026-1234,
deps: bump go-git and wip: halfway are all unrecognised, and all dropped unless you
pass WithIncludeAll.
The type match is case-insensitive: FEAT: and Feat: classify as feat. The space
after the colon is required — feat:no space does not parse.
Type to category¶
| Commit type | Category | Heading in the output |
|---|---|---|
feat |
CategoryFeature |
### Features |
fix |
CategoryFix |
### Bug Fixes |
perf |
CategoryPerformance |
### Performance Improvements |
build, chore, docs, refactor, revert, style |
CategoryOther |
### Other |
test, ci |
dropped | — |
| anything else (unrecognised type) | dropped | — |
| any of the above marked breaking | CategoryBreaking |
### Breaking Changes |
CategoryBreaking is the zero value of the Category type. An Entry you construct
yourself without setting Category is a breaking change.
Commits that never appear¶
Four kinds of commit produce no entry:
testandcicommits, dropped by type — unless the commit is also breaking, in which case it appears under Breaking Changes.- Merge commits, always. A commit with more than one parent contributes nothing, even when its message is a valid Conventional Commit and even when a release tag points at it.
- Unrecognised types, unless
WithIncludeAllis set. - Empty subjects. A commit message that is blank or whitespace-only is skipped.
How a breaking change is detected¶
Either signal marks an entry breaking, and either one overrides the category the type would otherwise have given it:
!before the colon —feat!: …orfeat(api)!: ….- A footer line,
BREAKING CHANGE:or the hyphenated synonymBREAKING-CHANGE:, anywhere in the commit body. The footer must begin at the start of a line; an indented or mid-line occurrence is not detected.
A breaking test/ci commit is not dropped — the breaking classification is applied
before the skip, so test(x): … with a BREAKING CHANGE: footer appears under Breaking
Changes.
The footer's own text is not carried into the entry. The description stays the subject line, and the explanation of what broke stays in the commit body where nothing reads it.
Scope and description¶
| Field | Source | Notes |
|---|---|---|
Scope |
The parenthesised scope, feat(cli): → cli |
Empty when absent. Spaces and slashes are accepted: feat(multi word scope), feat(a/b) |
Description |
The rest of the subject line | Trailing text is not truncated, cleaned or reflowed |
Raw |
Empty during generation. Only Parse sets it, to the original bullet line |
Descriptions are written into markdown verbatim. A description containing *, ** or a
markdown link keeps those characters, and they render as markdown in the output. A scope
containing a colon — fix(a:b): — is emitted as **a:b:** and will not parse back into a
Scope field, because the scope pattern excludes colons.
Where each release's commits come from¶
Classification decides what an entry says; attribution decides which release it lands in. Those are separate, and attribution is by ancestry rather than by date — see How commits are attributed to releases.