What changelog does not do¶
The quickest way to understand a small module is to know where it stops. Everything here
is absent on purpose, and none of it is planned; if you need one of these, you need
something around changelog, not a setting inside it.
There is no changelog command¶
changelog is a library. It ships no binary, has no CLI, reads no configuration file and
no environment variable. go install gives you nothing to run.
Generation takes a repository path and options as Go arguments; that is the entire input
surface. A tool that wants a changelog generate command builds one and calls
GenerateFromRepo from it — which is exactly how it is used inside the projects it was
extracted from.
It does not write, merge or update a file¶
GenerateFromRepo returns a string. It does not write CHANGELOG.md, does not look for
an existing one, and does not prepend a new release to a file you already have.
The reason is that generation is not incremental. The whole document is rendered from git history every time, so there is no "new part" to prepend — regenerating produces the current truth, and the previous file is superseded rather than appended to. Writing that string somewhere is a one-line decision your program is better placed to make than this module is.
The output format is fixed¶
No templates, no configuration, no theming. Heading levels, bullet characters, category names and their order are all constants — see generated output format for exactly what they are.
The output format is fixed because it is also an input format: what GenerateFromRepo
emits, Parse reads back. A configurable emitter would need a configurable parser to
match, and the two would drift apart the first time someone changed one of them. Keeping
one shape means a changelog this module wrote is a changelog it can still read.
If you need different markdown, parse the output into the model and render it yourself —
Changelog, Release and Entry are the stable interface, not the text.
There is no commit metadata in the output¶
No hashes, no dates, no authors, no links to commits, merge requests or issues, no compare URLs, no contributor list.
This is about the OUTPUT, and it still holds. CommitsFromRepo returns a Commit
carrying the hash and the raw commit type, but nothing it returns reaches the rendered
markdown, Entry gains no field, and Parse is unchanged. The rendered changelog carries
exactly what it did before, for exactly the reason below: it renders only what it can read
back. See reading commits and spec 0001.
That is a real limitation and worth being clear about, because tools like releaser-pleaser and git-cliff do emit those. The trade is deliberate: this module renders only what it can also read back, and a link is forge-specific — it needs a project URL, a host, and a path convention that differ between GitLab, GitHub and everything else. Adding them would mean either a template system or a forge dependency, and the module's whole premise is a dependency graph of four libraries.
If you want links, generate the model here and render the markdown in the tool that already knows your forge.
It does not filter by scope or author¶
You cannot ask for "only scope cli" or "only commits by someone". Every commit
reachable from the walk is classified and every surviving entry is emitted.
WithSinceTag and WithMaxReleases bound the output by release; WithPaths and
WithPathFilter bound it by the files a commit changes, which is what a repository
holding several components needs. Those are the axes. Filtering by anything else means
parsing the generated changelog — or walking the history yourself — and selecting
entries from the model.
It does not read the commit body¶
Only the subject line becomes an entry. The body is read for a single thing: whether a
BREAKING CHANGE: footer is present. The footer's text, any Refs: or Reviewed-by:
trailers, and the explanation of what actually broke are all discarded.
This is the one absence that costs a reader the most, and it is worth knowing before you write a careful breaking-change footer expecting to see it in the changelog. You will see the subject line under Breaking Changes and nothing else.
It does not validate anything¶
changelog reads history; it never rejects it. A commit that is not a Conventional Commit
is dropped, not reported. There is no lint mode, no "which commits were ignored" list, and
no count of what was skipped — a repository with no conforming commits and no tags
produces an empty string and a nil error, which looks exactly like a repository with no
commits at all.
If you need commits validated, that belongs in a commit hook or a CI check, where the author can still fix the message.
Its notion of a version is semver, and only semver¶
Tags that are not valid semver are invisible to it — see how commits are attributed to
releases. The test is
semver.IsValid on the tag with a v prefix added, so a calendar version, a sequential
release number or a date tag is only usable if it happens to be valid semver: 2026.7,
42 and 20260703 are accepted, 2026.07 (leading zero) and nightly are not. There is
no option to supply a version pattern.
What Parse will not do for you¶
- It does not sort.
Releasescomes back in the document's order, reversed — see accepted changelog formats. - It does not understand the keep-a-changelog section vocabulary.
Added,Changed,Removed,Deprecated,FixedandSecurityall become Other. - It does not read markdown structurally, so bullets inside fenced code blocks become entries.
- It does not fetch anything.
ParseFromArchivetakes anio.Reader; downloading the release archive is the caller's job, and the module makes no network calls of any kind.