diff --git a/hack/tools/release/notes/generator.go b/hack/tools/release/notes/generator.go index 9acb60cd6269..11e63f722889 100644 --- a/hack/tools/release/notes/generator.go +++ b/hack/tools/release/notes/generator.go @@ -49,7 +49,7 @@ type pr struct { // prLister returns a list of PRs. type prLister interface { - listPRs() ([]pr, error) + listPRs(previousRelease ref) ([]pr, error) } // notesEntry represents a line item for the release notes. @@ -67,25 +67,39 @@ type prProcessor interface { // entriesPrinter formats and outputs to stdout the notes // based on a list of entries. type entriesPrinter interface { - print([]notesEntry, int, string) + print([]notesEntry, int, string, ref) } -// run generates and prints the notes. -func (g *notesGenerator) run() error { - prs, err := g.lister.listPRs() +// run generating and prints the notes. +func (g *notesGenerator) run(previousReleaseRef ref) error { + if previousReleaseRef.value != "" { + previousReleasePRs, err := g.lister.listPRs(previousReleaseRef) + if err != nil { + return err + } + previousEntries := g.processor.process(previousReleasePRs) + + dependencies, err := g.dependenciesProcessor.generateDependencies(previousReleaseRef) + if err != nil { + return err + } + + g.printer.print(previousEntries, len(previousReleasePRs), dependencies, previousReleaseRef) + } + + prs, err := g.lister.listPRs(ref{}) if err != nil { return err } - entries := g.processor.process(prs) - dependencies, err := g.dependenciesProcessor.generateDependencies() + dependencies, err := g.dependenciesProcessor.generateDependencies(ref{}) if err != nil { return err } // Pass in length of PRs to printer as some PRs are excluded from the entries list - g.printer.print(entries, len(prs), dependencies) + g.printer.print(entries, len(prs), dependencies, ref{}) return nil } diff --git a/hack/tools/release/notes/list.go b/hack/tools/release/notes/list.go index fae9c823e5da..4cb5bcf1cbf2 100644 --- a/hack/tools/release/notes/list.go +++ b/hack/tools/release/notes/list.go @@ -54,9 +54,18 @@ func newGithubFromToPRLister(repo string, fromRef, toRef ref, branch string) *gi // between fromRef and toRef, discarding any PR not seeing in the commits list. // This ensures we don't include any PR merged in the same date range that // doesn't belong to our git timeline. -func (l *githubFromToPRLister) listPRs() ([]pr, error) { - log.Printf("Computing diff between %s and %s", l.fromRef, l.toRef) - diff, err := l.client.getDiffAllCommits(l.fromRef.value, l.toRef.value) +func (l *githubFromToPRLister) listPRs(previousReleaseRef ref) ([]pr, error) { + var ( + diff *githubDiff + err error + ) + if previousReleaseRef.value != "" { + log.Printf("Computing diff between %s and %s", previousReleaseRef.value, l.toRef) + diff, err = l.client.getDiffAllCommits(previousReleaseRef.value, l.toRef.value) + } else { + log.Printf("Computing diff between %s and %s", l.fromRef, l.toRef) + diff, err = l.client.getDiffAllCommits(l.fromRef.value, l.toRef.value) + } if err != nil { return nil, err } diff --git a/hack/tools/release/notes/main.go b/hack/tools/release/notes/main.go index 8e0227127545..0da080315904 100644 --- a/hack/tools/release/notes/main.go +++ b/hack/tools/release/notes/main.go @@ -38,6 +38,11 @@ This tool prints all the titles of all PRs in between to references. Use these as the base of your release notes. */ +const ( + betaRelease = "BETA RELEASE" + releaseCandidate = "RELEASE CANDIDATE" +) + func main() { cmd := newNotesCmd() if err := cmd.run(); err != nil { @@ -51,6 +56,7 @@ type notesCmdConfig struct { toRef string newTag string branch string + previousReleaseVersion string prefixAreaLabel bool deprecation bool addKubernetesVersionSupport bool @@ -64,6 +70,7 @@ func readCmdConfig() *notesCmdConfig { flag.StringVar(&config.toRef, "to", "", "The ref (tag, branch or commit to stop at. It must be formatted as heads/ for branches and tags/ for tags. If not set, it will default to branch.") flag.StringVar(&config.branch, "branch", "", "The branch to generate the notes from. If not set, it will be calculated from release.") flag.StringVar(&config.newTag, "release", "", "The tag for the new release.") + flag.StringVar(&config.previousReleaseVersion, "previous-release-version", "", "The tag for the previous beta release.") flag.BoolVar(&config.prefixAreaLabel, "prefix-area-label", true, "If enabled, will prefix the area label.") flag.BoolVar(&config.deprecation, "deprecation", true, "If enabled, will add a templated deprecation warning header.") @@ -86,7 +93,8 @@ func newNotesCmd() *notesCmd { } func (cmd *notesCmd) run() error { - if err := validateConfig(cmd.config); err != nil { + releaseType := releaseTypeFromNewTag(cmd.config.newTag) + if err := validateConfig(cmd.config, releaseType); err != nil { return err } @@ -100,8 +108,13 @@ func (cmd *notesCmd) run() error { from, to := parseRef(cmd.config.fromRef), parseRef(cmd.config.toRef) + var previousReleaseRef ref + if cmd.config.previousReleaseVersion != "" { + previousReleaseRef = parseRef(cmd.config.previousReleaseVersion) + } + printer := newReleaseNotesPrinter(cmd.config.repo, from.value) - printer.releaseType = releaseTypeFromNewTag(cmd.config.newTag) + printer.releaseType = releaseType printer.printDeprecation = cmd.config.deprecation printer.printKubernetesSupport = cmd.config.addKubernetesVersionSupport @@ -112,7 +125,7 @@ func (cmd *notesCmd) run() error { newDependenciesProcessor(cmd.config.repo, from.value, to.value), ) - return generator.run() + return generator.run(previousReleaseRef) } func releaseTypeFromNewTag(newTagConfig string) string { @@ -130,9 +143,9 @@ func releaseTypeFromNewTag(newTagConfig string) string { // If a new type is not defined, no warning banner will be printed. switch newTag.Pre[0].VersionStr { case "rc": - return "RELEASE CANDIDATE" + return releaseCandidate case "beta": - return "BETA RELEASE" + return betaRelease } return "" } @@ -150,7 +163,7 @@ func commandExists(cmd string) bool { return err == nil } -func validateConfig(config *notesCmdConfig) error { +func validateConfig(config *notesCmdConfig, releaseType string) error { if config.fromRef == "" && config.newTag == "" { return errors.New("at least one of --from or --release need to be set") } @@ -171,6 +184,12 @@ func validateConfig(config *notesCmdConfig) error { } } + if releaseType != "" { + if config.previousReleaseVersion == "" { + return errors.New("--previous-release-version need to be set with RELEASE CANDIDATE/BETA RELEASE tag") + } + } + return nil } diff --git a/hack/tools/release/notes/print.go b/hack/tools/release/notes/print.go index dc72d9d0c9cc..10e22dc6e6fd 100644 --- a/hack/tools/release/notes/print.go +++ b/hack/tools/release/notes/print.go @@ -37,6 +37,9 @@ var defaultOutputOrder = []string{ release.Unknown, } +var isExpanderAdded = false +var isPreReleasePrinted = false + // releaseNotesPrinter outputs the PR entries following // the right format for the release notes. type releaseNotesPrinter struct { @@ -57,7 +60,7 @@ func newReleaseNotesPrinter(repo, fromTag string) *releaseNotesPrinter { } // print outputs to stdout the release notes. -func (p *releaseNotesPrinter) print(entries []notesEntry, commitsInRelease int, dependencies string) { +func (p *releaseNotesPrinter) print(entries []notesEntry, commitsInRelease int, dependencies string, previousReleaseRef ref) { merges := map[string][]string{ release.Features: {}, release.Bugs: {}, @@ -75,11 +78,23 @@ func (p *releaseNotesPrinter) print(entries []notesEntry, commitsInRelease int, } } - if p.releaseType != "" { + if p.releaseType != "" && !isPreReleasePrinted { fmt.Printf("🚨 This is a %s. Use it only for testing purposes. If you find any bugs, file an [issue](https://github.com/%s/issues/new).\n", p.releaseType, p.repo) + isPreReleasePrinted = true } - if p.printKubernetesSupport { + if p.releaseType != "" && previousReleaseRef.value == "" { + // This will add the release notes expansion functionality for a pre-release version + fmt.Print(`
+More details about the release + +:warning: **RELEASE CANDIDATE NOTES** :warning: + +`) + isExpanderAdded = true + } + + if p.printKubernetesSupport && previousReleaseRef.value == "" { fmt.Print(`## 👌 Kubernetes version support - Management Cluster: v1.**X**.x -> v1.**X**.x @@ -106,7 +121,11 @@ REPLACE ME: A couple sentences describing the deprecation, including links to do `) } - fmt.Printf("## Changes since %s\n", p.fromTag) + if previousReleaseRef.value != "" { + fmt.Printf("## Changes since %s\n", previousReleaseRef.value) + } else { + fmt.Printf("## Changes since %s\n", p.fromTag) + } fmt.Printf("## :chart_with_upwards_trend: Overview\n") if commitsInRelease == 1 { @@ -139,18 +158,20 @@ REPLACE ME: A couple sentences describing the deprecation, including links to do switch key { case release.Documentation: - sort.Strings(mergeslice) - if len(mergeslice) == 1 { - fmt.Printf( - ":book: Additionally, there has been 1 contribution to our documentation and book. (%s) \n\n", - mergeslice[0], - ) - } else { - fmt.Printf( - ":book: Additionally, there have been %d contributions to our documentation and book. (%s) \n\n", - len(mergeslice), - strings.Join(mergeslice, ", "), - ) + if previousReleaseRef.value == "" { + sort.Strings(mergeslice) + if len(mergeslice) == 1 { + fmt.Printf( + ":book: Additionally, there has been 1 contribution to our documentation and book. (%s) \n\n", + mergeslice[0], + ) + } else { + fmt.Printf( + ":book: Additionally, there have been %d contributions to our documentation and book. (%s) \n\n", + len(mergeslice), + strings.Join(mergeslice, ", "), + ) + } } default: fmt.Println("## " + key) @@ -170,5 +191,10 @@ REPLACE ME: A couple sentences describing the deprecation, including links to do fmt.Print(dependencies) fmt.Println("") - fmt.Println("_Thanks to all our contributors!_ 😊") + if isExpanderAdded { + fmt.Print("
\n
\n") + } + if previousReleaseRef.value == "" { + fmt.Println("_Thanks to all our contributors!_ 😊") + } } diff --git a/hack/tools/release/notes/process.go b/hack/tools/release/notes/process.go index 1bc3fed534c6..d5f7c14b1689 100644 --- a/hack/tools/release/notes/process.go +++ b/hack/tools/release/notes/process.go @@ -117,10 +117,18 @@ func (g prEntriesProcessor) process(prs []pr) []notesEntry { return entries } -func (d dependenciesProcessor) generateDependencies() (string, error) { +func (d dependenciesProcessor) generateDependencies(previousRelease ref) (string, error) { repoURL := fmt.Sprintf("https://github.com/%s", d.repo) + + var fromTag string + if previousRelease.value != "" { + fromTag = previousRelease.value + } else { + fromTag = d.fromTag + } + deps, err := notes.NewDependencies().ChangesForURL( - repoURL, d.fromTag, d.toTag, + repoURL, fromTag, d.toTag, ) if err != nil { return "", err