Skip to content

Commit c472b28

Browse files
committed
Make release notes generation concurrent
Now that we retrieve the area labels from the PRs, the release notes generation can take up to 2 minutes for big releases like 1.5. Although in general this is not a problem since it should be a one time process, it does slow down the comms release team when iterating in the notes, adding missing labels, etc. It also makes development of the script painful when testing it manually. The default is 10 concurrent routines, but this can be changed with command flag. The default value of 10 doesn't seem to run into rate limiting problems. I had to bump it to a 100 and run the command multiple times in a row to be able to get rate limited.
1 parent aa93b82 commit c472b28

File tree

1 file changed

+105
-44
lines changed

1 file changed

+105
-44
lines changed

hack/tools/release/notes.go

Lines changed: 105 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"regexp"
3232
"sort"
3333
"strings"
34+
"sync"
3435
"time"
3536
)
3637

@@ -64,8 +65,9 @@ var (
6465

6566
fromTag = flag.String("from", "", "The tag or commit to start from.")
6667

67-
since = flag.String("since", "", "Include commits starting from and including this date. Accepts format: YYYY-MM-DD")
68-
until = flag.String("until", "", "Include commits up to and including this date. Accepts format: YYYY-MM-DD")
68+
since = flag.String("since", "", "Include commits starting from and including this date. Accepts format: YYYY-MM-DD")
69+
until = flag.String("until", "", "Include commits up to and including this date. Accepts format: YYYY-MM-DD")
70+
numWorkers = flag.Int("workers", 10, "Number of concurrent routines to process PR entries. If running into GitHub rate limiting, use 1.")
6971

7072
tagRegex = regexp.MustCompile(`^\[release-[\w-\.]*\]`)
7173
)
@@ -129,7 +131,7 @@ func getAreaLabel(merge string) (string, error) {
129131

130132
out, err := cmd.CombinedOutput()
131133
if err != nil {
132-
return "", err
134+
return "", fmt.Errorf("%s: %v", string(out), err)
133135
}
134136

135137
pr := &githubPullRequest{}
@@ -223,53 +225,49 @@ func run() int {
223225
}
224226
}
225227

226-
for _, c := range commits {
227-
body := trimTitle(c.body)
228-
var key, prNumber, fork string
229-
prefix, err := getAreaLabel(c.merge)
230-
if err != nil {
231-
fmt.Println(err)
232-
os.Exit(1)
233-
}
234-
switch {
235-
case strings.HasPrefix(body, ":sparkles:"), strings.HasPrefix(body, "✨"):
236-
key = features
237-
body = strings.TrimPrefix(body, ":sparkles:")
238-
body = strings.TrimPrefix(body, "✨")
239-
case strings.HasPrefix(body, ":bug:"), strings.HasPrefix(body, "🐛"):
240-
key = bugs
241-
body = strings.TrimPrefix(body, ":bug:")
242-
body = strings.TrimPrefix(body, "🐛")
243-
case strings.HasPrefix(body, ":book:"), strings.HasPrefix(body, "📖"):
244-
key = documentation
245-
body = strings.TrimPrefix(body, ":book:")
246-
body = strings.TrimPrefix(body, "📖")
247-
if strings.Contains(body, "CAEP") || strings.Contains(body, "proposal") {
248-
key = proposals
228+
results := make(chan releaseNoteEntryResult)
229+
commitCh := make(chan *commit)
230+
var wg sync.WaitGroup
231+
232+
wg.Add(*numWorkers)
233+
for i := 0; i < *numWorkers; i++ {
234+
go func() {
235+
for commit := range commitCh {
236+
processed := releaseNoteEntryResult{}
237+
processed.prEntry, processed.err = generateReleaseNoteEntry(commit)
238+
results <- processed
249239
}
250-
case strings.HasPrefix(body, ":seedling:"), strings.HasPrefix(body, "🌱"):
251-
key = other
252-
body = strings.TrimPrefix(body, ":seedling:")
253-
body = strings.TrimPrefix(body, "🌱")
254-
case strings.HasPrefix(body, ":warning:"), strings.HasPrefix(body, "⚠️"):
255-
key = warning
256-
body = strings.TrimPrefix(body, ":warning:")
257-
body = strings.TrimPrefix(body, "⚠️")
258-
default:
259-
key = unknown
240+
wg.Done()
241+
}()
242+
}
243+
244+
go func() {
245+
for _, c := range commits {
246+
commitCh <- c
247+
}
248+
close(commitCh)
249+
}()
250+
251+
go func() {
252+
wg.Wait()
253+
close(results)
254+
}()
255+
256+
for result := range results {
257+
if result.err != nil {
258+
fmt.Println(result.err)
259+
os.Exit(0)
260260
}
261261

262-
body = strings.TrimSpace(body)
263-
if body == "" {
262+
if result.prEntry.title == "" {
264263
continue
265264
}
266-
body = fmt.Sprintf("- %s: %s", prefix, body)
267-
_, _ = fmt.Sscanf(c.merge, "Merge pull request %s from %s", &prNumber, &fork)
268-
if key == documentation {
269-
merges[key] = append(merges[key], prNumber)
270-
continue
265+
266+
if result.prEntry.section == documentation {
267+
merges[result.prEntry.section] = append(merges[result.prEntry.section], result.prEntry.prNumber)
268+
} else {
269+
merges[result.prEntry.section] = append(merges[result.prEntry.section], result.prEntry.title)
271270
}
272-
merges[key] = append(merges[key], formatMerge(body, prNumber))
273271
}
274272

275273
// TODO Turn this into a link (requires knowing the project name + organization)
@@ -346,3 +344,66 @@ func commandExists(cmd string) bool {
346344
_, err := exec.LookPath(cmd)
347345
return err == nil
348346
}
347+
348+
// releaseNoteEntryResult is the result of processing a PR to create a release note item.
349+
// Used to aggregate the line item and error when processing concurrently.
350+
type releaseNoteEntryResult struct {
351+
prEntry *releaseNoteEntry
352+
err error
353+
}
354+
355+
// releaseNoteEntry represents a line item in the release notes.
356+
type releaseNoteEntry struct {
357+
title string
358+
section string
359+
prNumber string
360+
}
361+
362+
// generateReleaseNoteEntry processes a commit into a PR line item for the release notes.
363+
func generateReleaseNoteEntry(c *commit) (*releaseNoteEntry, error) {
364+
entry := &releaseNoteEntry{}
365+
entry.title = trimTitle(c.body)
366+
var fork string
367+
prefix, err := getAreaLabel(c.merge)
368+
if err != nil {
369+
return nil, err
370+
}
371+
372+
switch {
373+
case strings.HasPrefix(entry.title, ":sparkles:"), strings.HasPrefix(entry.title, "✨"):
374+
entry.section = features
375+
entry.title = strings.TrimPrefix(entry.title, ":sparkles:")
376+
entry.title = strings.TrimPrefix(entry.title, "✨")
377+
case strings.HasPrefix(entry.title, ":bug:"), strings.HasPrefix(entry.title, "🐛"):
378+
entry.section = bugs
379+
entry.title = strings.TrimPrefix(entry.title, ":bug:")
380+
entry.title = strings.TrimPrefix(entry.title, "🐛")
381+
case strings.HasPrefix(entry.title, ":book:"), strings.HasPrefix(entry.title, "📖"):
382+
entry.section = documentation
383+
entry.title = strings.TrimPrefix(entry.title, ":book:")
384+
entry.title = strings.TrimPrefix(entry.title, "📖")
385+
if strings.Contains(entry.title, "CAEP") || strings.Contains(entry.title, "proposal") {
386+
entry.section = proposals
387+
}
388+
case strings.HasPrefix(entry.title, ":seedling:"), strings.HasPrefix(entry.title, "🌱"):
389+
entry.section = other
390+
entry.title = strings.TrimPrefix(entry.title, ":seedling:")
391+
entry.title = strings.TrimPrefix(entry.title, "🌱")
392+
case strings.HasPrefix(entry.title, ":warning:"), strings.HasPrefix(entry.title, "⚠️"):
393+
entry.section = warning
394+
entry.title = strings.TrimPrefix(entry.title, ":warning:")
395+
entry.title = strings.TrimPrefix(entry.title, "⚠️")
396+
default:
397+
entry.section = unknown
398+
}
399+
400+
entry.title = strings.TrimSpace(entry.title)
401+
if entry.title == "" {
402+
return entry, nil
403+
}
404+
entry.title = fmt.Sprintf("- %s: %s", prefix, entry.title)
405+
_, _ = fmt.Sscanf(c.merge, "Merge pull request %s from %s", &entry.prNumber, &fork)
406+
entry.title = formatMerge(entry.title, entry.prNumber)
407+
408+
return entry, nil
409+
}

0 commit comments

Comments
 (0)