Skip to content

Commit e473b7c

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

File tree

6 files changed

+58
-27
lines changed

6 files changed

+58
-27
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: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,23 @@ func NewGatherer(ctx context.Context, opts *options.Options) (*Gatherer, error)
233233
if err != nil {
234234
return nil, fmt.Errorf("unable to create notes client: %w", err)
235235
}
236-
236+
if opts.ReleaseNoteRegex != "" {
237+
re, err := regexp.Compile(opts.ReleaseNoteRegex)
238+
if err != nil {
239+
return nil, fmt.Errorf("invalid --release-note-regex: %w", err)
240+
}
241+
hasNote := false
242+
for _, n := range re.SubexpNames() {
243+
if n == "note" {
244+
hasNote = true
245+
break
246+
}
247+
}
248+
if !hasNote {
249+
return nil, errors.New("--release-note-regex must define a named capture group 'note', e.g. (?P<note>...)")
250+
}
251+
opts.ReleaseNoteRegexCompiled = re
252+
}
237253
return &Gatherer{
238254
client: client,
239255
context: ctx,
@@ -394,27 +410,26 @@ func (g *Gatherer) ListReleaseNotes() (*ReleaseNotes, error) {
394410
return notes, nil
395411
}
396412

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-
}
406-
407-
if matchesFilter(s, emptyExps) {
408-
return "", errors.New("empty release note")
409-
}
410-
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```"),
413+
// noteTextFromString returns the text of the release note from a string (e.g. PR body).
414+
// If customRegex is non-nil, exps is set to just that; otherwise exps are the default
415+
// ```release-note / ```dev-release-note patterns. All patterns must capture a "note" group.
416+
func noteTextFromString(s string, customRegex *regexp.Regexp) (string, error) {
417+
var exps []*regexp.Regexp
418+
if customRegex != nil {
419+
exps = []*regexp.Regexp{customRegex}
420+
} else {
421+
emptyExps := []*regexp.Regexp{
422+
regexp.MustCompile("(?i)```release-notes?\\s*```\\s*"),
423+
}
424+
if matchesFilter(s, emptyExps) {
425+
return "", errors.New("empty release note")
426+
}
427+
exps = []*regexp.Regexp{
428+
regexp.MustCompile("(?sU)```release-notes?\\r\\n(?P<note>.+)\\r\\n```"),
429+
regexp.MustCompile("(?sU)```dev-release-notes?\\r\\n(?P<note>.+)"),
430+
regexp.MustCompile("(?sU)```\\r\\n(?P<note>.+)\\r\\n```"),
431+
regexp.MustCompile("(?sU)```release-notes?\n(?P<note>.+)\n```"),
432+
}
418433
}
419434

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

510525
prBody := pr.GetBody()
511526

512-
text, err := noteTextFromString(prBody)
527+
text, err := noteTextFromString(prBody, g.options.ReleaseNoteRegexCompiled)
513528
if err != nil {
514529
return nil, err
515530
}
@@ -791,7 +806,7 @@ func (g *Gatherer) ReleaseNoteForPullRequest(prNr int) (*ReleaseNote, error) {
791806

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

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

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)