Skip to content

Commit 1e0d5ea

Browse files
committed
release-notes: Add custom regex option
1 parent d98562e commit 1e0d5ea

File tree

6 files changed

+64
-24
lines changed

6 files changed

+64
-24
lines changed

cmd/release-notes/generate.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,13 @@ func addGenerateFlags(subcommand *cobra.Command) {
243243
[]string{},
244244
"specify a location to recursively look for release notes *.y[a]ml file mappings",
245245
)
246+
247+
subcommand.PersistentFlags().StringVar(
248+
&opts.ReleaseNoteRegex,
249+
"release-note-regex",
250+
"",
251+
"Optional regex to extract release note from PR body. Must have a named group 'note'. If unset, default ```release-note blocks are used.",
252+
)
246253
}
247254

248255
// addGenerate adds the generate subcomand to the main release notes cobra cmd.

pkg/notes/document/manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"RepoTags": ["registry.k8s.io/conformance-amd64:v1.16.0"]}]

pkg/notes/notes.go

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,29 @@ func NewGatherer(ctx context.Context, opts *options.Options) (*Gatherer, error)
234234
return nil, fmt.Errorf("unable to create notes client: %w", err)
235235
}
236236

237+
if opts.ReleaseNoteRegex != "" {
238+
re, err := regexp.Compile(opts.ReleaseNoteRegex)
239+
if err != nil {
240+
return nil, fmt.Errorf("invalid --release-note-regex: %w", err)
241+
}
242+
243+
hasNote := false
244+
245+
for _, n := range re.SubexpNames() {
246+
if n == "note" {
247+
hasNote = true
248+
249+
break
250+
}
251+
}
252+
253+
if !hasNote {
254+
return nil, errors.New("--release-note-regex must define a named capture group 'note', e.g. (?P<note>...)")
255+
}
256+
257+
opts.ReleaseNoteRegexCompiled = re
258+
}
259+
237260
return &Gatherer{
238261
client: client,
239262
context: ctx,
@@ -394,27 +417,28 @@ func (g *Gatherer) ListReleaseNotes() (*ReleaseNotes, error) {
394417
return notes, nil
395418
}
396419

397-
// noteTextFromString returns the text of the release note given a string which
398-
// may contain the commit message, the PR description, etc.
399-
// This is generally the content inside the ```release-note ``` stanza.
400-
func noteTextFromString(s string) (string, error) {
401-
// check release note is not empty
402-
// Matches "release-notes" block with no meaningful content (ex. only whitespace, empty, just newlines)
403-
emptyExps := []*regexp.Regexp{
404-
regexp.MustCompile("(?i)```release-notes?\\s*```\\s*"),
405-
}
420+
// noteTextFromString returns the text of the release note from a string (e.g. PR body).
421+
// If customRegex is non-nil, exps is set to just that; otherwise exps are the default
422+
// ```release-note / ```dev-release-note patterns. All patterns must capture a "note" group.
423+
func noteTextFromString(s string, customRegex *regexp.Regexp) (string, error) {
424+
var exps []*regexp.Regexp
425+
if customRegex != nil {
426+
exps = []*regexp.Regexp{customRegex}
427+
} else {
428+
emptyExps := []*regexp.Regexp{
429+
regexp.MustCompile("(?i)```release-notes?\\s*```\\s*"),
430+
}
406431

407-
if matchesFilter(s, emptyExps) {
408-
return "", errors.New("empty release note")
409-
}
432+
if matchesFilter(s, emptyExps) {
433+
return "", errors.New("empty release note")
434+
}
410435

411-
exps := []*regexp.Regexp{
412-
// (?s) is needed for '.' to be matching on newlines, by default that's disabled
413-
// we need to match ungreedy 'U', because after the notes a `docs` block can occur
414-
regexp.MustCompile("(?sU)```release-notes?\\r\\n(?P<note>.+)\\r\\n```"),
415-
regexp.MustCompile("(?sU)```dev-release-notes?\\r\\n(?P<note>.+)"),
416-
regexp.MustCompile("(?sU)```\\r\\n(?P<note>.+)\\r\\n```"),
417-
regexp.MustCompile("(?sU)```release-notes?\n(?P<note>.+)\n```"),
436+
exps = []*regexp.Regexp{
437+
regexp.MustCompile("(?sU)```release-notes?\\r\\n(?P<note>.+)\\r\\n```"),
438+
regexp.MustCompile("(?sU)```dev-release-notes?\\r\\n(?P<note>.+)"),
439+
regexp.MustCompile("(?sU)```\\r\\n(?P<note>.+)\\r\\n```"),
440+
regexp.MustCompile("(?sU)```release-notes?\n(?P<note>.+)\n```"),
441+
}
418442
}
419443

420444
for _, exp := range exps {
@@ -509,7 +533,7 @@ func (g *Gatherer) ReleaseNoteFromCommit(result *Result) (*ReleaseNote, error) {
509533

510534
prBody := pr.GetBody()
511535

512-
text, err := noteTextFromString(prBody)
536+
text, err := noteTextFromString(prBody, g.options.ReleaseNoteRegexCompiled)
513537
if err != nil {
514538
return nil, err
515539
}
@@ -791,7 +815,7 @@ func (g *Gatherer) ReleaseNoteForPullRequest(prNr int) (*ReleaseNote, error) {
791815

792816
// If we didn't match the exclusion filter, try to extract the release note from the PR.
793817
// If we can't extract the release note, consider that the PR is invalid and take the next one
794-
s, err := noteTextFromString(prBody)
818+
s, err := noteTextFromString(prBody, g.options.ReleaseNoteRegexCompiled)
795819
if err != nil && !doNotPublish {
796820
return nil, fmt.Errorf("PR #%d does not seem to contain a valid release note: %w", pr.GetNumber(), err)
797821
}
@@ -867,7 +891,7 @@ func (g *Gatherer) notesForCommit(commit *gogithub.RepositoryCommit) (*Result, e
867891

868892
// If we didn't match the exclusion filter, try to extract the release note from the PR.
869893
// If we can't extract the release note, consider that the PR is invalid and take the next one
870-
s, err := noteTextFromString(prBody)
894+
s, err := noteTextFromString(prBody, g.options.ReleaseNoteRegexCompiled)
871895
if err != nil {
872896
logrus.Infof("PR #%d does not seem to contain a valid release note, skipping", pr.GetNumber())
873897

pkg/notes/notes_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func TestNoteTextFromString(t *testing.T) {
321321
},
322322
},
323323
} {
324-
tc.expect(noteTextFromString(tc.input))
324+
tc.expect(noteTextFromString(tc.input, nil))
325325
}
326326
}
327327

pkg/notes/notes_v2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func (g *Gatherer) buildReleaseNote(pair *commitPrPair) (*ReleaseNote, error) {
162162
return nil, nil
163163
}
164164

165-
text, err := noteTextFromString(prBody)
165+
text, err := noteTextFromString(prBody, g.options.ReleaseNoteRegexCompiled)
166166
if err != nil {
167167
logrus.WithFields(logrus.Fields{
168168
"sha": pair.Commit.Hash.String(),

pkg/notes/options/options.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"os"
2323
"path/filepath"
24+
"regexp"
2425
"strings"
2526

2627
"github.com/sirupsen/logrus"
@@ -136,6 +137,13 @@ type Options struct {
136137

137138
// IncludeLabels can be used to filter PRs by labels so only PRs with one or more specified labels are included.
138139
IncludeLabels []string
140+
141+
// ReleaseNoteRegex optionally overrides how release note text is extracted from PR bodies.
142+
// When set, this regex is used instead of the default ```release-note blocks. Must define a named capture "note".
143+
ReleaseNoteRegex string
144+
145+
// ReleaseNoteRegexCompiled is the compiled form of ReleaseNoteRegex, set when a gatherer is created.
146+
ReleaseNoteRegexCompiled *regexp.Regexp
139147
}
140148

141149
type RevisionDiscoveryMode string

0 commit comments

Comments
 (0)