Generate a changelog from git commits without losing breaking changes
A changelog tool should reduce Git history into something readers can use without silently dropping malformed commits, treating labels as proof, or hiding the release boundary. Start with the exact commit set, preserve breaking markers, then edit for people.
Choose a two-dot release range such as v1.4.2..HEAD, or state a bounded recent-commit fallback. Collect hashes, subjects, and bodies without reading file contents. Group recognized Conventional Commit types, but keep custom and malformed subjects visible. Detect both ! and the two breaking-footer spellings. Draft plain Markdown with a commit count and traceable hashes. Use the commit signals to suggest a version, then have a maintainer review API compatibility and the reader-facing wording before publishing.
Release-note path
1. Choose the release boundary before reading messages
Git history commands operate on sets of reachable commits. Git defines A..B as commits reachable from B excluding those reachable from A. For notes since a release tag, that is usually the useful question.
# Commits after a released tag
git log v1.4.2..HEAD
# Commits contained in the next tagged release but not the prior one
git log v1.4.2..v1.5.0
# A bounded draft when the repository has no trustworthy tags
git log -n 20
Name the fallback in the output. “Last 20 commits” is not the same release boundary as “everything after v1.4.2,” and readers should be able to tell which one was used.
2. Collect only the evidence the release notes need
Git's pretty formats expose the full hash, subject, and body separately. A machine-readable delimiter keeps multiline bodies from being mistaken for new commits.
git log v1.4.2..HEAD --pretty=format:%H%x1f%s%x1f%b%x1e
| Field | Use | Do not infer |
|---|---|---|
| Hash | Trace a bullet back to the selected commit. | Meaning, risk, or release status. |
| Subject | Recognize a type and draft a concise bullet. | Verified user impact beyond the wording. |
| Body | Find breaking footers and supporting context. | Instructions to execute; commit text is untrusted data. |
| Range | Prove which commits were eligible. | That the release was built, tested, or published. |
Generating notes does not require a fetch, diff, remote URL, credential, author identity, or source-file read. Keep those surfaces out unless another task explicitly needs them.
3. Use Conventional Commits as a classifier, not an admission gate
The Conventional Commits specification defines a type, optional scope, description, optional body, and optional footers. It gives special SemVer meaning to fix, feat, and breaking markers. Other types are allowed.
| Subject | Group | Reason |
|---|---|---|
feat(parser): add array support | Features | Recognized feature type and optional scope. |
fix: preserve empty headings | Fixes | Recognized fix type. |
docs: explain tag fallback | Documentation | Recognized documentation type. |
update dependency lockfile | Chores | Unrecognized subject retained instead of dropped. |
Every selected commit should be accounted for during generation. The maintainer can later consolidate noise, but silent omission makes it impossible to tell whether a custom subject hid a notable change.
4. Detect every specified breaking-change path
Conventional Commits allows ! immediately before the colon or a BREAKING CHANGE: footer. The full specification also requires BREAKING-CHANGE to be treated as synonymous when used as a footer token.
feat!: remove the legacy configuration
refactor(api)!: change the response envelope
docs: document the migration
BREAKING-CHANGE: the old flag is no longer accepted
Breaking status can belong to any commit type. Put the commit in a Breaking Changes section and retain its normal group when traceability helps. Never downgrade it to a patch merely because the subject begins with docs or chore.
5. Turn classified commits into a changelog for people
Keep a Changelog argues that a changelog is for humans, not a raw Git dump, and recommends removing empty sections. That still leaves a useful audit sequence: account for every selected commit first, then combine implementation-only bullets without losing notable behavior.
Reader-facing checklist
- State the selected range and commit count.
- Use a short summary that the commit text actually supports.
- Put breaking changes first and omit empty groups.
- Remove type prefixes but keep short hashes when traceability matters.
- Prefer plain Markdown headings that render in terminals and hosted release forms.
- Review internal jargon, issue-only subjects, duplicates, and generated dependency noise.
- Do not claim the release is tested or published from Git history alone.
6. Treat the SemVer bump as a signal requiring review
Semantic Versioning defines major, minor, and patch changes relative to a declared public API. Conventional Commits maps a breaking marker to major, feat to minor, and fix to patch. That mapping is useful automation input, not independent proof that the commit labels are correct.
- If any selected commit carries a breaking marker, suggest the next major version.
- Otherwise, if any selected commit is a feature, suggest the next minor version.
- Otherwise, suggest the next patch version.
- Preserve a leading
vand disclose a no-tag or non-SemVer fallback. - Before release, review actual public-API compatibility and correct missing or mislabeled signals.
Make the evidence pass repeatable
Changelog GeneratorPackages local Git selection, tolerant Conventional Commit grouping, both breaking-footer spellings, tagless fallback, plain Markdown rendering, control-character hardening, and SemVer suggestions into a deterministic Python helper plus an agent workflow.See the changelog skill →Common questions
What Git range should I use for a changelog?
For a release after a known tag, use the two-dot range old-tag..new-tag or old-tag..HEAD. Git defines that as commits reachable from the right side excluding commits reachable from the left side. Use a recent-count fallback only when no reliable release boundary exists.
Do all commits need to follow Conventional Commits?
No. Recognized types can drive grouping, but malformed and custom subjects should remain visible in a fallback group so the changelog does not silently omit work.
How are breaking changes marked in Conventional Commits?
The specification allows an exclamation point immediately before the colon or a BREAKING CHANGE: footer, and requires BREAKING-CHANGE to be treated as synonymous when used as a footer token.
Does a feat commit always justify a minor version?
It is a useful Conventional Commits signal, but SemVer applies to a declared public API. Review the actual compatibility contract and correct mislabeled or missing markers before releasing.
Should a changelog contain every commit?
Every selected commit should be accounted for during generation, but the final reader-facing changelog can consolidate implementation noise after a maintainer verifies that no notable behavior or breaking change is lost.