Generate a changelog from your git commits.
Paste git log output or Conventional Commits. You get a Keep a Changelog section, sorted into Added, Changed, Fixed, Removed and Security with breaking changes flagged, and the same changes as plain release notes.
Short guide
How to generate a changelog from git commits
Your history already knows what changed between two releases. Three steps turn it into a changelog: find the previous release, copy the commits since then, and edit what the tool sorted for you. Editing matters: Keep a Changelog 2.0.0 says plainly “Do not use a list of commits as a changelog”, because a commit records a step in the code and a changelog entry records what changed for the people using it.
1. Find the previous release and list the commits since then
git describe --tags --abbrev=0 prints the most recent tag reachable from where you are (--tags includes lightweight tags). A..B means the commits in B that aren't in A, so v1.4.0..HEAD is everything since 1.4.0. --no-merges leaves out merge commits.
# The previous release: the most recent tag reachable from HEAD
git describe --tags --abbrev=0
# v1.4.0
# Commits since then, one line each (tags shown)
git log v1.4.0..HEAD --oneline --no-merges --decorate
# Full messages, so BREAKING CHANGE: footers come along
git log v1.4.0..HEAD --no-merges2. Copy them straight to your clipboard
One command finds the tag and copies everything since it. Paste the result into the box above.
# macOS
git log "$(git describe --tags --abbrev=0)..HEAD" --no-merges | pbcopy
# Linux (X11, with xclip installed)
git log "$(git describe --tags --abbrev=0)..HEAD" --no-merges | xclip -selection clipboard
# Windows PowerShell
git log "$(git describe --tags --abbrev=0)..HEAD" --no-merges | Set-Clipboard| When you want | Run |
|---|---|
| Exactly what went into one past release | git log v1.3.0..v1.4.0 --no-merges |
| A first release, before any tags | git log --no-merges |
| One entry per merged pull request (merge-commit workflow) | git log v1.4.0..HEAD --first-parent |
| The last 50 commits with tags shown; the tool stops at the previous release's tag | git log -50 --oneline --decorate |
3. Paste, then edit
The tool sorts each commit into a type of change, flags breaking changes and leaves out merges, release commits and chores. Then it's your turn: merge lines that describe one change, drop what users won't notice, and add upgrade steps where something breaks. As Keep a Changelog puts it, “machines can draft, but humans curate.”
The mapping
How Conventional Commits map to changelog sections
The same rules as the free GitHub Action and GitLab component, so what you see here is what they write into your releases.
| Commit | Goes to | Example |
|---|---|---|
feat: feature: | AddedA new feature: a minor version under SemVer. | feat: add dark mode |
fix: bugfix: hotfix: | FixedA bug fix: a patch version. | fix: handle empty input |
perf: refactor: revert: | ChangedChanges to how something that exists behaves. | perf: cache parsed configs |
security: | SecurityVulnerabilities, so people know to upgrade. | security: redact tokens in logs |
deprecate: | DeprecatedSay which version removes it, in the entry. | deprecate: the legacy option |
remove: | RemovedUsually breaking too: add a !. | remove: the v1 endpoints |
! before the colon, or a BREAKING CHANGE: footer | Its own type, marked Breaking: in placeFooter text becomes the migration steps; release notes list it first. A major version. | feat(api)!: return ISO 8601 dates |
docs: deps: | No type of changeLeft out, or listed under Changed if you choose. | docs: explain webhooks |
chore: ci: build: test: style: | Left outInternal. “Include chores” keeps them. | chore: tidy scripts |
Other types (wip:, ops:…) | By first word, else ChangedRead like a plain subject. A standard type says more. | wip: onboarding flow |
| No type, starts with Add, Introduce, Support… | AddedPlain subjects are sorted by their first word. | Add CSV export |
| Starts with Fix, Resolve, Prevent… | Fixed | Fix the login redirect |
| Starts with Remove, Drop, Delete… | Removed | Remove the legacy exporter |
| Starts with Deprecate… | Deprecated | Deprecate the v1 client |
| Anything else (Update, Improve, Rename…) | Changed | Update the pricing page copy |
| Merge commits and release commits | Left outBookkeeping, not changes. | Merge branch 'main' into feature |
Types are matched without regard to case. Under Semantic Versioning, feat means a minor release, fix a patch, and any breaking change a major one. More on writing commits that make good notes in release notes from Conventional Commits.
Common mistakes
What goes wrong, and the fix.
The tool catches some of these for you. The rest are habits worth changing.
Pasting the whole history
Without a range, every old release ends up in this one. Give git the range since the last tag, or add --decorate so tags show and the tool can stop at the previous release by itself. Piped output (to pbcopy, a file) drops tags unless you pass --decorate.
git log --oneline
git log v1.4.0..HEAD --oneline --decorate
Using --oneline with BREAKING CHANGE: footers
A BREAKING CHANGE: footer is in the commit body, and one-line output only has the subject. The breaking change goes unflagged and its migration steps are lost. A ! in the subject survives either way.
git log v1.4.0..HEAD --oneline
git log v1.4.0..HEAD --no-merges
Almost-Conventional Commits
The type is followed by an optional (scope), an optional !, a colon and a space, in that order. Near misses are read as plain subjects and sorted by their first word; the tool tells you which ones.
feat:add export · feat!(api): … · feat(api) add export
feat: add export · feat(api)!: … · feat(api): add export
Hiding real changes as chore:
chore, ci, build, test and style are left out of a changelog because they're internal. If users notice the change, give it the type that says so: feat, fix, perf.
chore: speed up cold starts
perf: speed up cold starts
Subjects nobody can act on
Each subject becomes a line in the changelog. Say what changed for the person upgrading, not what you did in the code.
fix: bug · updates · wip
fix: keep query strings when following redirects
Squash merges with free-form titles
When you squash-merge, the pull request title becomes the commit. Check titles against the convention in review, or with a CI check such as commitlint.
Some fixes (#118)
fix(cli): show a clear error when the config is missing (#118)
No tags in a CI checkout
CI often clones with a shallow history and no tags, so git describe has nothing to find. Fetch them first, or set fetch-depth: 0 on actions/checkout.
fatal: No names found, cannot describe anything.
git fetch --tags --unshallow
Shipping the draft unedited
Keep a Changelog 2.0.0 is clear that a list of commits isn't a changelog. The sorting and breaking-change flags are done for you; the wording is yours to finish.
Paste, commit, tag
Paste, merge related lines, drop internal ones, add upgrade steps, then tag
From scripts and agents
The same changelog, one command away.
A free endpoint and MCP tool with the same rules as this page. No key, no account.
- Pipe git log to itPlain text in, a Keep a Changelog section or release notes out.
- Agents ask directlyCoding agents connected to the LogPulse MCP server call commits_to_changelog after they ship.
- Nothing keptDeterministic, no AI, nothing stored. 30 calls a minute per IP address.
git log "$(git describe --tags --abbrev=0)..HEAD" --no-merges \
| curl -s -X POST "https://release-ripple-roadmap.lovable.app/api/public/v1/commits-to-changelog?format=markdown&version=1.5.0" \
-H "Content-Type: text/plain" --data-binary @-# same command, with
# ?format=notes
# or ?format=json for the
# sections, breaking changes
# and every commit's verdictclaude mcp add --transport http logpulse https://release-ripple-roadmap.lovable.app/mcp
# then ask:
# "Write the changelog for
# everything since the last tag"Endpoint: POST https://release-ripple-roadmap.lovable.app/api/public/v1/commits-to-changelog · a raw text/plain body or JSON { log | commits[] }. Read the docs.
Questions
Changelogs from git commits
Sources