feat: generate compare links for changelog references

This commit is contained in:
Micheal Wilkinson
2026-03-20 23:31:39 +00:00
parent 22780869af
commit 48b36bddcd

View File

@@ -9,6 +9,7 @@ package vociferate
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv" "strconv"
@@ -16,9 +17,9 @@ import (
) )
const ( const (
defaultVersionFile = "release-version" defaultVersionFile = "release-version"
defaultVersionExpr = `^\s*([^\r\n]+)\s*$` defaultVersionExpr = `^\s*([^\r\n]+)\s*$`
defaultChangelog = "changelog.md" defaultChangelog = "changelog.md"
defaultUnreleasedTemplate = "### Breaking\n\n### Added\n\n### Changed\n\n### Removed\n\n### Fixed\n" defaultUnreleasedTemplate = "### Breaking\n\n### Added\n\n### Changed\n\n### Removed\n\n### Fixed\n"
) )
@@ -246,7 +247,7 @@ func updateChangelog(rootDir, version, releaseDate, changelogPath string) error
updated := text[:afterHeader] + "\n" + defaultUnreleasedTemplate + "\n" + newSection + text[nextSectionStart:] updated := text[:afterHeader] + "\n" + defaultUnreleasedTemplate + "\n" + newSection + text[nextSectionStart:]
repoURL, ok := deriveRepositoryURL(rootDir) repoURL, ok := deriveRepositoryURL(rootDir)
if ok { if ok {
updated = addChangelogLinks(updated, repoURL) updated = addChangelogLinks(updated, repoURL, rootDir)
} }
if err := os.WriteFile(path, []byte(updated), 0o644); err != nil { if err := os.WriteFile(path, []byte(updated), 0o644); err != nil {
return fmt.Errorf("write changelog: %w", err) return fmt.Errorf("write changelog: %w", err)
@@ -426,7 +427,7 @@ func normalizeRepoURL(remoteURL string) (string, bool) {
return "", false return "", false
} }
func addChangelogLinks(text, repoURL string) string { func addChangelogLinks(text, repoURL, rootDir string) string {
repoURL = strings.TrimSuffix(strings.TrimSpace(repoURL), "/") repoURL = strings.TrimSuffix(strings.TrimSpace(repoURL), "/")
if repoURL == "" { if repoURL == "" {
return text return text
@@ -456,16 +457,60 @@ func addChangelogLinks(text, repoURL string) string {
text = strings.Join(lines[:cutAt], "\n") + "\n" text = strings.Join(lines[:cutAt], "\n") + "\n"
// Build and append reference link definitions. // Build and append reference link definitions.
linkDefs := []string{fmt.Sprintf("[Unreleased]: %s/src/branch/main", repoURL)} releasedMatches := releasedSectionRe.FindAllStringSubmatch(text, -1)
for _, match := range releasedSectionRe.FindAllStringSubmatch(text, -1) { releasedVersions := make([]string, 0, len(releasedMatches))
for _, match := range releasedMatches {
if len(match) >= 2 { if len(match) >= 2 {
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s/releases/tag/v%s", match[1], repoURL, match[1])) releasedVersions = append(releasedVersions, match[1])
} }
} }
linkDefs := make([]string, 0, len(releasedVersions)+1)
if len(releasedVersions) > 0 {
latest := releasedVersions[0]
linkDefs = append(linkDefs, fmt.Sprintf("[Unreleased]: %s/compare/v%s...main", repoURL, latest))
} else {
linkDefs = append(linkDefs, fmt.Sprintf("[Unreleased]: %s/src/branch/main", repoURL))
}
firstCommitShort, hasFirstCommit := firstCommitShortHash(rootDir)
for i, version := range releasedVersions {
if i+1 < len(releasedVersions) {
previousVersion := releasedVersions[i+1]
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s/compare/v%s...v%s", version, repoURL, previousVersion, version))
continue
}
if hasFirstCommit {
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s/compare/%s...v%s", version, repoURL, firstCommitShort, version))
continue
}
linkDefs = append(linkDefs, fmt.Sprintf("[%s]: %s/compare/v%s...main", version, repoURL, version))
}
return strings.TrimRight(text, "\n") + "\n\n" + strings.Join(linkDefs, "\n") + "\n" return strings.TrimRight(text, "\n") + "\n\n" + strings.Join(linkDefs, "\n") + "\n"
} }
func firstCommitShortHash(rootDir string) (string, bool) {
command := exec.Command("git", "-C", rootDir, "rev-list", "--max-parents=0", "--abbrev-commit", "HEAD")
output, err := command.Output()
if err != nil {
return "", false
}
commit := strings.TrimSpace(string(output))
if commit == "" {
return "", false
}
if strings.Contains(commit, "\n") {
commit = strings.SplitN(commit, "\n", 2)[0]
}
return commit, true
}
func parseSemver(version string) (semver, error) { func parseSemver(version string) (semver, error) {
parts := strings.Split(strings.TrimSpace(version), ".") parts := strings.Split(strings.TrimSpace(version), ".")
if len(parts) != 3 { if len(parts) != 3 {