4
4
"context"
5
5
"fmt"
6
6
"regexp"
7
+ "sort"
7
8
"strings"
8
9
9
10
"github.com/google/go-github/v68/github"
@@ -77,7 +78,7 @@ func (g *Generator) getReferencedPRs(ctx context.Context, _ []*github.Commit) ([
77
78
var prs []* github.PullRequest
78
79
79
80
// Search for PRs referenced in commit messages
80
- query := fmt .Sprintf (" repo:%s/%s is:pr is:merged" , g .owner , g .repo )
81
+ query := fmt .Sprintf (` repo:%s/%s is:pr is:merged label:"release-note","release-note-needed"` , g .owner , g .repo )
81
82
result , _ , err := g .client .Search .Issues (ctx , query , & github.SearchOptions {
82
83
TextMatch : true ,
83
84
ListOptions : github.ListOptions {
@@ -110,14 +111,41 @@ func (g *Generator) generateChangelog(prs []*github.PullRequest) string {
110
111
kind := g .getPRKind (pr )
111
112
buckets [kind ] = append (buckets [kind ], pr )
112
113
}
113
-
114
- // Print each bucket
115
- for kind , header := range kindHeaders {
116
- if prs , ok := buckets [kind ]; ok && len (prs ) > 0 {
117
- changelog .WriteString (fmt .Sprintf ("\n ## %s\n \n " , header ))
118
- for _ , pr := range prs {
119
- changelog .WriteString (fmt .Sprintf ("- %s (#%d)\n " , * pr .Title , * pr .Number ))
114
+ // sort the buckets by kind to ensure deterministic output
115
+ kinds := make ([]string , 0 , len (kindHeaders ))
116
+ for kind := range kindHeaders {
117
+ kinds = append (kinds , kind )
118
+ }
119
+ sort .Strings (kinds )
120
+
121
+ // build the changelog
122
+ for _ , kind := range kinds {
123
+ header := kindHeaders [kind ]
124
+ prs := buckets [kind ]
125
+ if len (prs ) == 0 {
126
+ continue
127
+ }
128
+ changelog .WriteString (fmt .Sprintf ("\n ## %s\n \n " , header ))
129
+ for _ , pr := range prs {
130
+ body := pr .GetBody ()
131
+ if body == "" {
132
+ continue
120
133
}
134
+ // attempt to extract a release-note from the PR body
135
+ match := releaseNoteRE .FindStringSubmatch (body )
136
+ if len (match ) < 2 {
137
+ // skip PRs that have the release-note label but no release-note body.
138
+ // TODO(tim): this shouldn't be possible with the labeler being a required
139
+ // check, but we'll check for it anyway in case users have manually added
140
+ // the label to a PR.
141
+ continue
142
+ }
143
+ note := match [1 ]
144
+ if note == "" {
145
+ // TODO(tim): we should probably log this as an error as this is unexpected
146
+ continue
147
+ }
148
+ changelog .WriteString (fmt .Sprintf ("- %s (#%d)\n " , note , pr .GetNumber ()))
121
149
}
122
150
}
123
151
@@ -129,9 +157,9 @@ func (g *Generator) getPRKind(pr *github.PullRequest) string {
129
157
// Check labels first
130
158
for _ , label := range pr .Labels {
131
159
switch * label .Name {
132
- case "kind/new- feature" :
160
+ case "kind/feature" , "kind/new_feature " :
133
161
return "new_feature"
134
- case "kind/bug " :
162
+ case "kind/fix" , "kind/bug_fix " :
135
163
return "bug_fix"
136
164
case "kind/breaking_change" :
137
165
return "breaking_change"
@@ -142,20 +170,5 @@ func (g *Generator) getPRKind(pr *github.PullRequest) string {
142
170
}
143
171
}
144
172
145
- // Fall back to title-based detection
146
- title := strings .ToLower (* pr .Title )
147
- switch {
148
- case strings .Contains (title , "feat" ) || strings .Contains (title , "feature" ):
149
- return "new_feature"
150
- case strings .Contains (title , "fix" ) || strings .Contains (title , "bug" ):
151
- return "bug_fix"
152
- case strings .Contains (title , "break" ) || strings .Contains (title , "breaking" ):
153
- return "breaking_change"
154
- case strings .Contains (title , "doc" ) || strings .Contains (title , "docs" ):
155
- return "documentation"
156
- case strings .Contains (title , "perf" ) || strings .Contains (title , "performance" ):
157
- return "performance"
158
- default :
159
- return "other"
160
- }
173
+ return "other"
161
174
}
0 commit comments