changelog#
CHANGELOG maintenance.
The header format is load-bearing, not a style preference: ci-helpers extracts
GitHub Release notes from CHANGELOG.md by finding the section for the tag being
released. Any other header shape and the release notes silently fall back to an
auto-generated commit list. The canonical form is:
YYYY-MM-DD, a space, an em-dash, a space, then the version with an optional
v prefix. changelog_check_header is what stops that from rotting.
Functions#
changelog_check_header <file>- Purpose: Verify the newest release header conforms. An
## [Unreleased]section at the top is allowed and skipped over. -
Returns: 0 and the version on success; 2 for a missing file; 1 when the header does not conform, with the offending line and the expected shape printed. An ASCII hyphen where the format needs an em-dash is called out specifically, because it is the common near-miss.
-
changelog_extract <file> <version> - Purpose: Print the body of the section for
version, without its header, for use as release notes. Thevprefix is optional on both sides. - Matching: the version must match whole.
0.2.0does not selectv10.2.0orv0.2.0-rc.1. Only the first version in a header names the section, so## 2026-09-01 — v1.0.0 (supersedes 0.9.0)is the 1.0.0 section and never the 0.9.0 one. A version is two or more dot-separated numbers, so## [1.2] - 2026-02-02is the 1.2 section. A dotted date is skipped when picking that first version -## 2026.09.01 — v1.1.9is the 1.1.9 section - unless it is the only version-like token in the header. - A
##line inside a fenced code block (```or~~~) is part of the section, not the start of the next one. Fences follow CommonMark: up to three spaces of indent and at least three backticks or tildes; a backtick fence's info string cannot contain a backtick, so a line such as```make test``` nowis inline code, not a fence; a fence closes only on a line of the same character, at least as long as the opener, with nothing but spaces or tabs after it. - Unlike CommonMark, a fence that is never closed is treated as ordinary text rather than running to the end of the file. In a changelog an unclosed fence is a typo, and running it to the end would hide every older release header, so those versions would silently have no section.
-
Returns: 0 and the body; 2 for missing arguments or a missing file; 1 when there is no such section.
-
changelog_has_entries <text> - Purpose: Tell a written section from an empty one.
textis a section body aschangelog_extractprints it. -
Returns: 0 when it has a line that is neither blank nor a heading; 1 otherwise. The template
changelog_new_sectionwrites (empty###headings) returns 1. -
changelog_new_section <file> <version> [--date YYYY-MM-DD] [--section <name>]... - Purpose: Insert a new release section at the top, above the newest existing one and below any
## [Unreleased]placeholder. Creates the file with a title when it does not exist. - Args:
--date- defaults to today, UTC.--section- repeatable. Defaults toAdded,Changed,Fixed,Security.
- Returns: 0 on success, including when a section for that version already exists - it refuses to run twice, so it is safe in a release script that gets rerun. 2 for a non-semver version or an option without its value; 1 when the rewrite would produce an empty file.
Environment#
No environment variables are required.
Dependencies#
None beyond coreutils.
Examples#
shlib_import changelog
changelog_check_header CHANGELOG.md || exit 1
changelog_new_section CHANGELOG.md 1.4.0
notes="$(changelog_extract CHANGELOG.md 1.4.0)"
changelog_has_entries "$notes" || echo "1.4.0 has not been written up yet"
PowerShell#
ps/lib/changelog.ps1 mirrors this module with the same function names, and
returns $null where the Bash functions return a non-zero status.