Skip to content
Open
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
7 changes: 7 additions & 0 deletions cmd/release-notes/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,13 @@ func addGenerateFlags(subcommand *cobra.Command) {
[]string{},
"specify a location to recursively look for release notes *.y[a]ml file mappings",
)

subcommand.PersistentFlags().StringVar(
&opts.ReleaseNoteRegex,
"release-note-regex",
"",
"Optional regex to extract release note from PR body. Must have a named group 'note'. If unset, default ```release-note blocks are used.",
)
}

// addGenerate adds the generate subcomand to the main release notes cobra cmd.
Expand Down
1 change: 1 addition & 0 deletions pkg/notes/document/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"RepoTags": ["registry.k8s.io/conformance-amd64:v1.16.0"]}]
68 changes: 46 additions & 22 deletions pkg/notes/notes.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,29 @@ func NewGatherer(ctx context.Context, opts *options.Options) (*Gatherer, error)
return nil, fmt.Errorf("unable to create notes client: %w", err)
}

if opts.ReleaseNoteRegex != "" {
re, err := regexp.Compile(opts.ReleaseNoteRegex)
if err != nil {
return nil, fmt.Errorf("invalid --release-note-regex: %w", err)
}

hasNote := false

for _, n := range re.SubexpNames() {
if n == "note" {
hasNote = true

break
}
}

if !hasNote {
return nil, errors.New("--release-note-regex must define a named capture group 'note', e.g. (?P<note>...)")
}

opts.ReleaseNoteRegexCompiled = re
}

return &Gatherer{
client: client,
context: ctx,
Expand Down Expand Up @@ -394,27 +417,28 @@ func (g *Gatherer) ListReleaseNotes() (*ReleaseNotes, error) {
return notes, nil
}

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

if matchesFilter(s, emptyExps) {
return "", errors.New("empty release note")
}
if matchesFilter(s, emptyExps) {
return "", errors.New("empty release note")
}

exps := []*regexp.Regexp{
// (?s) is needed for '.' to be matching on newlines, by default that's disabled
// we need to match ungreedy 'U', because after the notes a `docs` block can occur
regexp.MustCompile("(?sU)```release-notes?\\r\\n(?P<note>.+)\\r\\n```"),
regexp.MustCompile("(?sU)```dev-release-notes?\\r\\n(?P<note>.+)"),
regexp.MustCompile("(?sU)```\\r\\n(?P<note>.+)\\r\\n```"),
regexp.MustCompile("(?sU)```release-notes?\n(?P<note>.+)\n```"),
exps = []*regexp.Regexp{
regexp.MustCompile("(?sU)```release-notes?\\r\\n(?P<note>.+)\\r\\n```"),
regexp.MustCompile("(?sU)```dev-release-notes?\\r\\n(?P<note>.+)"),
regexp.MustCompile("(?sU)```\\r\\n(?P<note>.+)\\r\\n```"),
regexp.MustCompile("(?sU)```release-notes?\n(?P<note>.+)\n```"),
}
}

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

prBody := pr.GetBody()

text, err := noteTextFromString(prBody)
text, err := noteTextFromString(prBody, g.options.ReleaseNoteRegexCompiled)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -791,7 +815,7 @@ func (g *Gatherer) ReleaseNoteForPullRequest(prNr int) (*ReleaseNote, error) {

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

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

Expand Down
2 changes: 1 addition & 1 deletion pkg/notes/notes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ func TestNoteTextFromString(t *testing.T) {
},
},
} {
tc.expect(noteTextFromString(tc.input))
tc.expect(noteTextFromString(tc.input, nil))
}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/notes/notes_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (g *Gatherer) buildReleaseNote(pair *commitPrPair) (*ReleaseNote, error) {
return nil, nil
}

text, err := noteTextFromString(prBody)
text, err := noteTextFromString(prBody, g.options.ReleaseNoteRegexCompiled)
if err != nil {
logrus.WithFields(logrus.Fields{
"sha": pair.Commit.Hash.String(),
Expand Down
8 changes: 8 additions & 0 deletions pkg/notes/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -136,6 +137,13 @@ type Options struct {

// IncludeLabels can be used to filter PRs by labels so only PRs with one or more specified labels are included.
IncludeLabels []string

// ReleaseNoteRegex optionally overrides how release note text is extracted from PR bodies.
// When set, this regex is used instead of the default ```release-note blocks. Must define a named capture "note".
ReleaseNoteRegex string

// ReleaseNoteRegexCompiled is the compiled form of ReleaseNoteRegex, set when a gatherer is created.
ReleaseNoteRegexCompiled *regexp.Regexp
}

type RevisionDiscoveryMode string
Expand Down