Skip to content

fix(tag): pick the latest tag by semver, not lexicographic order - #53

Open
martient wants to merge 2 commits into
developfrom
fix/tag-latest-semver-sort
Open

martient wants to merge 2 commits into
developfrom
fix/tag-latest-semver-sort

Conversation

@martient

@martient martient commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

Two related correctness bugs in the tag/version-bump pipeline, both surfaced while diagnosing why committy tag cut a broken v1.11.0 release:

  1. find_latest_tag() used lexicographic order instead of semver. git2's tag list sorts as plain strings, so "v1.9.1" sorts after "v1.10.0" and "v1.11.0" (ASCII '9' > '1'). Confirmed by instrumentation that it always resolved to v1.9.1 on this repo regardless of newer releases. Fix: parse tags as semver and take the actual maximum, skipping non-semver tags (e.g. per-package tags like docs@0.2.1).

  2. Bump-type regexes never matched squash-merge commit bodies. MAJOR_REGEX/MINOR_REGEX/PATCH_REGEX anchored the commit type at true line start. GitHub/GitLab squash-merge bodies render each original subject as "* type(scope): message", so the anchored regexes never matched — every squash-merged PR silently fell back to Patch, hiding real feat/breaking-change commits. This is exactly what happened cutting v2.0.0: the squashed PR Develop #52 body contained * feat(tag)!: bump version files by default when tagging, a real breaking change that only surfaced as Major once this was fixed.

Both bugs independently under-versioned every release cut from a squash-merged history, and together were causing the repo's own release automation to be silently wrong.

Test plan

  • find_latest_tag_uses_semver_order_not_lexicographic_order — reproduces the exact v1.9.1/v1.10.0/v1.11.0 ordering bug against a real git2 repo
  • find_latest_tag_ignores_non_semver_tags — covers per-package tag markers
  • bump_regexes_match_squash_merge_bullet_lines — covers bulleted squash lines for all three regexes, plus non-squashed lines and a mid-line false-positive guard
  • cargo test, cargo fmt --all -- --check, cargo clippy --all-targets --all-features all pass

find_latest_tag() sorted git2's tag list lexicographically and took
the last entry. That puts "v1.9.1" after "v1.10.0" and "v1.11.0"
(ASCII '9' > '1'), so every multi-package tag run compared commits
against a release from weeks earlier than the true latest, producing
wrong bump types and affected-package detection. Parse tags as semver
and take the actual maximum instead, ignoring non-semver tags like
per-package "docs@0.2.1" markers.
MAJOR_REGEX/MINOR_REGEX/PATCH_REGEX anchored the commit type at true
line start. GitHub/GitLab squash-merge bodies render each original
subject as "* type(scope): message", so none of the bulleted lines
ever matched and every squash-merged PR silently fell back to a Patch
bump, hiding real feat/breaking-change commits. This is exactly what
happened cutting v2.0.0: the squashed PR #52 body contained
"* feat(tag)!: bump version files by default when tagging", a real
breaking change, which only surfaced as Major once this was fixed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant