pub fn release_range_start(
project_root: &Path,
baseline_tag: &str,
) -> Result<String, VersionError>Expand description
Resolve the commit range start for D-08’s conventional-commit classifier,
given the baseline tag name (e.g. "v2.0.0").
This exists because every release in this repository squash-merges
develop into main, so no develop-side commit is ever an ancestor of
the release tag it was squashed into — a -X ours sync merge-back
restores ancestry in the OTHER direction only (the tag becomes an
ancestor of HEAD, which is what makes D-07’s --merged HEAD
reachability filter work), but the commits the tag released stay
outside its ancestry forever. A literal baseline..HEAD range therefore
re-includes the entire pre-release history on every subsequent ship —
measured live 2026-07-27: v2.0.0..HEAD is 677 non-merge commits (62
feat), against 5 (0 feat) for the anchored range this function
computes. See 25-01-PLAN.md’s <measured_correction>.
Anchor rule (generalized 2026-07-28 to fix CR-03 — 25-REVIEW.md,
25-VERIFICATION.md GAP 2):
- Walk
git rev-list --ancestry-path --reverse <tag>..HEADoldest-first. For each candidate commitCin order: ifChas no first parent (a root commit), or the baseline tag is NOT an ancestor ofC’s first parent,Cis where the tag’s line joinedHEAD’s line — returnCimmediately. - If every candidate’s first parent already descends from the tag, the
tag already sat on this mainline throughout (the ordinary,
non-squashed case, e.g.
v1.8.0..v1.8.1) — return the tag unchanged. - If the ancestry path is empty, the tag is at
HEAD— return the tag.
CR-03 — the previous rule inspected only the ancestry path’s FIRST
commit (C1). When a commit lands directly on trunk between the tag and
the sync-merge-back (a hotfix pushed straight to main), that
intervening commit becomes C1; its first parent IS the tag commit, so
merge-base --is-ancestor <tag> <tag> is trivially true, and the old
rule wrongly concluded the tag already sat on mainline — returning the
literal tag..HEAD range and re-admitting pre-release develop history.
Walking the FULL path instead of just C1 fixes this: the sync merge
itself still fails the ancestor test and is returned once the walk
reaches it.
Anchoring at the LAST merge commit instead (a plausible-looking
alternative) is WRONG on this repository. GitFlow::merge_feature_into_develop
(git.rs:86) merges every phase branch into develop with git merge --no-ff, so ordinary post-release feature work also produces merge
commits on the ancestry path — not just the sync-merge-back. Anchoring at
the last one would silently truncate the range at that later feature
merge instead of the sync merge, dropping any commits between the two
from classification — a feat!: in that position would be dropped
unnoticed, the exact false negative D-09 exists to prevent. See
tests::feature_merge_after_sync_merge_does_not_move_the_anchor.