diff --git a/tools/relnotes/relnotes.go b/tools/relnotes/relnotes.go index 369b29d327..c1684ba13d 100644 --- a/tools/relnotes/relnotes.go +++ b/tools/relnotes/relnotes.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// The relnotes command summarizes the Go changes in Gerrit marked with +// RELNOTE annotations for the release notes. package main import ( @@ -11,27 +13,32 @@ import ( "fmt" "io/ioutil" "log" + "path/filepath" "regexp" "sort" "strings" "time" + "unicode" "golang.org/x/build/maintner" "golang.org/x/build/maintner/godata" ) var ( - sinceCl = flag.Int("cl", -1, "the gerrit change number of the first CL to include in the output. Only changes submitted more recently than 'cl' will be included.") - project = flag.String("project", "vscode-go", "name of the golang project") - mdMode = flag.Bool("md", false, "write MD output") - exclFile = flag.String("exclude-from", "", "optional path to changelog MD file. If specified, any 'CL NNNN' occurence in the content will cause that CL to be excluded from this tool's output.") + milestone = flag.String("milestone", "", "milestone associated with the release") + filterDirs = flag.String("dirs", "", "comma-separated list of directories that should be touched for a CL to be considered relevant") + sinceCL = flag.Int("cl", -1, "the gerrit change number of the first CL to include in the output. Only changes submitted more recently than 'cl' will be included.") + project = flag.String("project", "vscode-go", "name of the golang project") + mdMode = flag.Bool("md", false, "write MD output") + exclFile = flag.String("exclude-from", "", "optional path to changelog MD file. If specified, any 'CL NNNN' occurence in the content will cause that CL to be excluded from this tool's output.") ) // change is a change that has occurred since the last release. type change struct { CL *maintner.GerritCL Note string // the part after RELNOTE= - Issues []*maintner.GitHubIssue + Issues []*issue + pkg string } func (c change) TextLine() string { @@ -40,6 +47,19 @@ func (c change) TextLine() string { return fmt.Sprintf("https://golang.org/cl/%d: %s", c.CL.Number, subj) } +type issue struct { + *maintner.GitHubIssue + repo string + owner string +} + +func (i *issue) link() string { + if i.owner == "golang" && i.repo == "go" { + return fmt.Sprintf("https://golang.org/issue/%v", i.Number) + } + return fmt.Sprintf("https://github.com/%s/%s/issues/%v", i.owner, i.repo, i.Number) +} + func main() { flag.Parse() @@ -57,10 +77,17 @@ func main() { log.Fatal(err) } + var dirs []string + for _, dir := range strings.FieldsFunc(*filterDirs, func(r rune) bool { + return unicode.IsSpace(r) || r == ',' + }) { + dirs = append(dirs, filepath.ToSlash(dir)) + } + ger := corpus.Gerrit() // Find the cutoff time for changes to include. - cutoff := time.Date(2020, time.August, 1, 00, 00, 00, 0, time.UTC) + start := time.Date(2020, time.August, 1, 00, 00, 00, 0, time.UTC) ger.ForeachProjectUnsorted(func(gp *maintner.GerritProject) error { if gp.Server() != "go.googlesource.com" || gp.Project() != *project { return nil @@ -69,21 +96,21 @@ func main() { if cl.Status != "merged" { return nil } - if *sinceCl >= 0 { - if int(cl.Number) == *sinceCl { - cutoff = cl.Commit.CommitTime + if *sinceCL >= 0 { + if int(cl.Number) == *sinceCL { + start = cl.Commit.CommitTime } - } else if cl.Branch() == "release" && cl.Commit.CommitTime.After(cutoff) { + } else if cl.Branch() == "release" && cl.Commit.CommitTime.After(start) { // Try to figure out when the last release was fmt.Println(cl.Commit.CommitTime) - cutoff = cl.Commit.CommitTime + start = cl.Commit.CommitTime } return nil }) return nil }) - changes := map[string][]change{} // keyed by pkg + var changes []*change authors := map[*maintner.GitPerson]bool{} ger.ForeachProjectUnsorted(func(gp *maintner.GerritProject) error { if gp.Server() != "go.googlesource.com" || gp.Project() != *project { @@ -97,7 +124,7 @@ func main() { if cl.Status != "merged" { return nil } - if cl.Commit.CommitTime.Before(cutoff) { + if cl.Commit.CommitTime.Before(start) { // Was in a previous release; not for this one. return nil } @@ -106,17 +133,43 @@ func main() { return nil } + // Check that at least one file is in a relevant directory before + // adding the CL. + if len(dirs) > 0 { + var found bool + for _, file := range cl.Commit.Files { + for _, dir := range dirs { + if strings.Contains(file.File, dir) { + found = true + break + } + } + } + if !found { + return nil + } + } + // try to determine type from issue labels - var issues []*maintner.GitHubIssue + var issues []*issue for _, ref := range cl.GitHubIssueRefs { - issues = append(issues, ref.Repo.Issue(ref.Number)) + i := ref.Repo.Issue(ref.Number) + // Don't include pull requests. + if i.PullRequest { + continue + } + issues = append(issues, &issue{ + repo: ref.Repo.ID().Repo, + owner: ref.Repo.ID().Owner, + GitHubIssue: i, + }) } - pkg := clPackage(cl) - changes[pkg] = append(changes[pkg], change{ + changes = append(changes, &change{ Note: clRelNote(cl), CL: cl, Issues: issues, + pkg: clPackage(cl), }) authors[cl.Owner()] = true @@ -125,58 +178,84 @@ func main() { return nil }) - var pkgs []string - for pkg, changes := range changes { - pkgs = append(pkgs, pkg) - sort.Slice(changes, func(i, j int) bool { - return changes[i].CL.Number < changes[j].CL.Number - }) - } - sort.Strings(pkgs) + sort.Slice(changes, func(i, j int) bool { + return changes[i].CL.Number < changes[j].CL.Number + }) if *mdMode { fmt.Printf("## TODO: version - ") now := time.Now() fmt.Printf("%s\n\n", now.Format("2 Jan, 2006")) fmt.Printf("### Changes\n\n") - mdPrintChanges(pkgs, changes) + mdPrintChanges(changes, true) + + fmt.Printf("### Issues\n\n") + mdPrintIssues(changes, *milestone) fmt.Printf("\n### Thanks\n\n") - mdPrintContributers(authors) + mdPrintContributors(authors) } else { - for _, pkg := range pkgs { - for _, change := range changes[pkg] { - fmt.Printf(" %s\n", change.TextLine()) - } + for _, change := range changes { + fmt.Printf(" %s\n", change.TextLine()) } } } -func mdPrintChanges(pkgs []string, changes map[string][]change) { - for _, pkg := range pkgs { - for _, change := range changes[pkg] { - fmt.Printf("- ") - content := change.CL.Subject() - if change.Note != "" && change.Note != "yes" && change.Note != "y" { - // Note contains content - content = change.Note - } +func mdPrintChanges(changes []*change, byPackage bool) { + printChange := func(change *change) { + fmt.Printf("- ") + content := change.CL.Subject() + if change.Note != "" && change.Note != "yes" && change.Note != "y" { + // Note contains content + content = change.Note + } - fmt.Printf("%s", content) - if len(change.CL.GitHubIssueRefs) > 0 { - fmt.Printf(" (") - for i, ref := range change.CL.GitHubIssueRefs { + fmt.Printf("%s", content) + if len(change.CL.GitHubIssueRefs) > 0 { + fmt.Printf(" (") + for i, ref := range change.CL.GitHubIssueRefs { - if i == 0 { - fmt.Printf("[Issue %d](https://github.com/%s/issues/%d)", ref.Number, ref.Repo.ID().String(), ref.Number) - } else { - fmt.Printf(", [%d](https://github.com/%s/issues/%d)", ref.Number, ref.Repo.ID().String(), ref.Number) - } + if i == 0 { + fmt.Printf("[Issue %d](https://github.com/%s/issues/%d)", ref.Number, ref.Repo.ID().String(), ref.Number) + } else { + fmt.Printf(", [%d](https://github.com/%s/issues/%d)", ref.Number, ref.Repo.ID().String(), ref.Number) } - fmt.Printf(")") } - fmt.Printf(" \n", change.CL.Number) + fmt.Printf(")") + } + fmt.Printf(" \n", change.CL.Number) + } + // Group CLs by package or by number order. + if byPackage { + pkgMap := map[string][]*change{} + for _, change := range changes { + pkgMap[change.pkg] = append(pkgMap[change.pkg], change) + } + for _, changes := range pkgMap { + for _, change := range changes { + printChange(change) + } + } + } else { + for _, change := range changes { + printChange(change) + } + } +} + +func mdPrintIssues(changes []*change, milestone string) { + var issues []*issue + for _, change := range changes { + issues = append(issues, change.Issues...) + } + sort.Slice(issues, func(i, j int) bool { + return issues[i].Number < issues[j].Number + }) + for _, issue := range issues { + if !issue.Closed { + continue } + fmt.Printf("%s: %s\n", issue.link(), issue.Milestone.Title) } } @@ -212,7 +291,7 @@ func clRelNote(cl *maintner.GerritCL) string { return "" } -func mdPrintContributers(authors map[*maintner.GitPerson]bool) { +func mdPrintContributors(authors map[*maintner.GitPerson]bool) { var names []string for author := range authors { names = append(names, author.Name())