Parse existing release notes¶
The parsing half turns a changelog document back into a typed model, so a tool can read
what was released rather than only render it. Useful for showing "what changed" from a
downloaded release, or inspecting a CHANGELOG.md in CI.
Parse a markdown string¶
cl, err := changelog.Parse(rawNotes)
fmt.Println(cl.FromVersion, "→", cl.ToVersion)
for _, rel := range cl.Releases {
fmt.Println("##", rel.Version, "-", len(rel.Entries), "entries")
}
Releases comes back oldest-first, on the assumption that the document was written
newest-first. FromVersion is the first release of that reversed slice and ToVersion
the last — they are positions in the document, not a semver comparison, so a document
written oldest-first yields both the wrong way round.
Tell "no changelog" apart from "unreadable changelog"¶
Empty input is "no changelog": a Changelog with no releases and a nil error. Non-empty
input in which nothing was recognised returns ErrNoReleases, so a caller such as a
self-update breaking-change check can tell the two apart:
cl, err := changelog.Parse(rawNotes)
if errors.Is(err, changelog.ErrNoReleases) {
// present the raw notes instead of a parsed summary
}
One case sits between them: a document with bullets but no headings parses into a single
release whose Version is empty, and returns no error. If an unversioned release matters
to you, check for it.
Formats it accepts¶
Parse reads both changelog families in the wild:
- This module's own output —
# vX.Y.Zheadings,*bullets,**scope:**entries. Round-trips exactly. - The releaser-pleaser / keep-a-changelog family —
## [vX.Y.Z](url)link-wrapped headings with trailing dates,-bullets,**scope**:entries with the colon outside the bold, and the inline**BREAKING**:marker. A release-please### ⚠ BREAKING CHANGESheading maps correctly too.
Section headings are matched against a fixed list, and the keep-a-changelog vocabulary
is not on it: Added, Changed, Deprecated, Removed, Fixed and Security all
fall through to Other. Every recognised heading, and what each maps to, is in accepted
changelog formats.
Parse from a release archive¶
Releases often ship the changelog inside a gzip/tar bundle. ParseFromArchive extracts and
parses it in one step:
cl, err := changelog.ParseFromArchive(resp.Body)
if err != nil {
return err
}
if cl == nil {
// no CHANGELOG.md in the archive — fall back to the release API
return fetchNotesFromAPI(ctx, version)
}
Check for nil. An archive with no changelog in it is not an error — the design lets
you fall back to another source — but the returned pointer is nil, and calling
cl.HasBreakingChanges() on it panics. Parse never does this; only ParseFromArchive.
The reader must be a gzip-compressed tar. A changelog larger than 10 MB is truncated silently at that point, and whatever follows is lost without an error.
Query the model¶
if cl.HasBreakingChanges() {
for _, e := range cl.BreakingChanges() {
fmt.Printf("BREAKING %s: %s\n", e.Scope, e.Description)
}
}
features := cl.EntriesByCategory(changelog.CategoryFeature)
fixes := cl.EntriesByCategory(changelog.CategoryFix)
BreakingChanges and EntriesByCategory gather matching entries across every release
in the changelog, flattened — they answer "what changed across this range", not "what
changed in v1.2.0". For per-release answers, walk cl.Releases and filter
rel.Entries yourself.
Render a summary¶
FormatSummary produces a concise, terminal-friendly summary — breaking changes first
under a warning banner, then Features / Bug Fixes / Performance / Other:
WARNING: Breaking changes detected!
BREAKING: config: renamed the timeout key
Features:
- cli: add a --json flag
Bug Fixes:
- http: retry on 503
It is plain text, not markdown, and it is flattened across releases like the query helpers.
A nil or empty changelog formats to the empty string, so it is safe to call
unconditionally.
Limitations¶
Parse is a line scanner, not a markdown parser: bullets inside fenced code blocks become
entries, trailing commit links stay in the description text, and a version heading below
level 2 is read as a section heading. It never sorts, never fetches, and never validates.
The full list is in what changelog does not
do.