DEV Community

Cover image for My git changelog showed every pull request twice — a commit range isn't a change list
InApp
InApp

Posted on Originally published at imapp.blogspot.com

My git changelog showed every pull request twice — a commit range isn't a change list

Last release cycle I generated notes for our own service the naive way: git log v0.9.0..v0.10.0, format the subjects, done. The output listed 12 commits. We had merged 6 pull requests.

Every feature appeared twice — once as Merge pull request #41 from feature/retry-queue and once as the actual commit subject buried inside that branch.

Why: on a merge-commit repo, base..target walks both parents. The merge commit and the branch's own commits are all reachable from the target tag, so both get listed. Each PR is N+1 entries, not one.

Fix attempt one: --no-merges. Cleaner, but the merge subjects carried the PR number and title, so dropping them lost the #41 references from the notes.

Fix attempt two: --first-parent. On a merge-commit repo this collapses each PR to exactly its merge commit — one entry per feature, PR number preserved. It worked until a teammate hotfixed main directly with two plain commits. No merge node, so nothing collapsed, and the notes were a mix of polished PR entries and raw commits. Mixed merge culture means no single flag saves you.

What actually worked:

  • Treat merge commits as boundaries, not entries — walk first-parent and describe each merge as one change
  • Fall back to plain commits for direct-push segments
  • For squash-merge repos, the naive diff was already perfect by construction: one commit per PR

The real lesson: a commit range is not a change list. The same repo can produce 2x entries, N+1 entries, or a clean list depending entirely on the team's merge convention. Any changelog tooling has to know the convention or normalize across all three.

I ended up folding the first-parent-with-boundaries logic into the Git Changelog Generator I run at https://x402.freeq.one/tools/changelog_git.html so I stop re-learning this every release. Last run's numbers: 142 commits in the raw range, 63 were merges, final changelog: 79 entries — one per actual change.

Top comments (0)