Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions tools/release/enrich-release-notes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import (
"github.com/grafana/alloy/tools/release/internal/version"
)

// commitPattern matches a commit SHA in markdown link format: "([abc1234](https://github.com/.../commit/...))"
// It captures the short SHA from the link text, regardless of surrounding context.
var commitPattern = regexp.MustCompile(`\(\[([a-f0-9]{7,40})\]\(https://github\.com/[^)]+\)\)`)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the main difference is the absence of \s*$


// commitAuthorsQuery is the GraphQL query to fetch all authors for a commit.
const commitAuthorsQuery = `query($owner: String!, $repo: String!, $oid: GitObjectID!) {
repository(owner: $owner, name: $repo) {
Expand Down Expand Up @@ -109,13 +113,20 @@ func main() {
fmt.Println("✅ Release notes updated successfully")
}

// extractCommitSHA extracts a commit SHA from a changelog line.
// Returns the SHA if found, or an empty string if no commit link is present.
func extractCommitSHA(line string) string {
matches := commitPattern.FindStringSubmatch(line)
if matches == nil {
return ""
}
return matches[1]
}

// addContributorInfo adds contributor usernames to changelog entries.
// It extracts commit SHAs from each line and looks up the author + co-authors.
func addContributorInfo(ctx context.Context, client *gh.Client, body string) string {
lines := strings.Split(body, "\n")
// Match commit SHA in markdown link format: "([abc1234](https://github.com/.../commit/...))"
// This captures the short SHA from the link text
commitPattern := regexp.MustCompile(`\(\[([a-f0-9]{7,40})\]\(https://github\.com/[^)]+\)\)\s*$`)

for i, line := range lines {
if strings.TrimSpace(line) == "" {
Expand All @@ -124,12 +135,11 @@ func addContributorInfo(ctx context.Context, client *gh.Client, body string) str

fmt.Printf(" Processing line %d: %s\n", i, line)

matches := commitPattern.FindStringSubmatch(line)
if matches == nil {
sha := extractCommitSHA(line)
if sha == "" {
fmt.Printf(" No commit SHA found in line %d\n", i)
continue
}
sha := matches[1]

contributors, err := getCommitContributors(ctx, client, sha)
if err != nil {
Expand Down
120 changes: 120 additions & 0 deletions tools/release/enrich-release-notes/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package main

import "testing"

func TestExtractCommitSHA(t *testing.T) {
tests := []struct {
name string
input string
expectedSHA string
}{
{
name: "commit link at end of line",
input: "* HTTP/2 is no longer always disabled in loki.write ([#5267](https://github.com/grafana/alloy/issues/5267)) ([1c97c2d](https://github.com/grafana/alloy/commit/1c97c2d569fcda2f6761534150b063d1404dc388))",
expectedSHA: "1c97c2d",
},
{
name: "commit link with closes reference after",
input: "* Invalid handling of `id` in `foreach` when using discovery components ([#5322](https://github.com/grafana/alloy/issues/5322)) ([61fe184](https://github.com/grafana/alloy/commit/61fe1845d3b109992cbb0ec99a062ac113c1a411)), closes [#5297](https://github.com/grafana/alloy/issues/5297)",
expectedSHA: "61fe184",
},
{
name: "commit link with extra notes after",
input: "* Some fix ([deadbeef](https://github.com/grafana/alloy/commit/deadbeef)) - extra notes here",
expectedSHA: "deadbeef",
},
{
name: "full 40-character SHA",
input: "* Fix bug ([abc1234567890def1234567890abc1234567890](https://github.com/grafana/alloy/commit/abc1234567890def1234567890abc1234567890))",
expectedSHA: "abc1234567890def1234567890abc1234567890",
},
{
name: "no parens around link",
input: "* No parens [abc1234](https://github.com/grafana/alloy/commit/abc1234)",
expectedSHA: "",
},
{
name: "just a PR reference",
input: "* Just a PR reference (#1234)",
expectedSHA: "",
},
{
name: "empty line",
input: "",
expectedSHA: "",
},
{
name: "line with no commit info",
input: "### Bug Fixes",
expectedSHA: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sha := extractCommitSHA(tt.input)
if sha != tt.expectedSHA {
t.Errorf("extractCommitSHA(%q) = %q, want %q", tt.input, sha, tt.expectedSHA)
}
})
}
}

func TestFormatAttribution(t *testing.T) {
tests := []struct {
name string
usernames []string
expected string
}{
{
name: "single user",
usernames: []string{"alice"},
expected: "(@alice)",
},
{
name: "multiple users",
usernames: []string{"alice", "bob", "charlie"},
expected: "(@alice, @bob, @charlie)",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := formatAttribution(tt.usernames)
if result != tt.expected {
t.Errorf("formatAttribution(%v) = %q, want %q", tt.usernames, result, tt.expected)
}
})
}
}

func TestDeriveDocTag(t *testing.T) {
tests := []struct {
name string
tag string
expected string
}{
{
name: "standard release",
tag: "v1.15.2",
expected: "v1.15",
},
{
name: "release candidate",
tag: "v1.2.3-rc.0",
expected: "v1.2",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := deriveDocTag(tt.tag)
if err != nil {
t.Fatalf("deriveDocTag(%q) returned error: %v", tt.tag, err)
}
if result != tt.expected {
t.Errorf("deriveDocTag(%q) = %q, want %q", tt.tag, result, tt.expected)
}
})
}
}
Loading