Skip to content

Commit

Permalink
tools/relnotes: make some changes to generate release notes for gopls
Browse files Browse the repository at this point in the history
This change makes it easier to use this tool to generate release notes
for gopls as well. Filter directories are needed for the x/tools repo,
and the milestone check helps to make sure that issues are accurately
categorized.

Change-Id: Ib8287247f8f40b621f100413777c6ac220087687
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/337489
Trust: Rebecca Stambler <[email protected]>
Run-TryBot: Rebecca Stambler <[email protected]>
TryBot-Result: kokoro <[email protected]>
Reviewed-by: Suzy Mueller <[email protected]>
  • Loading branch information
stamblerre committed Jul 27, 2021
1 parent 2e8e703 commit 80c1b5c
Showing 1 changed file with 131 additions and 52 deletions.
183 changes: 131 additions & 52 deletions tools/relnotes/relnotes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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 {
Expand All @@ -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()

Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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(" <!-- CL %d -->\n", change.CL.Number)
fmt.Printf(")")
}
fmt.Printf(" <!-- CL %d -->\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)
}
}

Expand Down Expand Up @@ -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())
Expand Down

0 comments on commit 80c1b5c

Please sign in to comment.