cmd/relnote: add HTML output and delta-from-last-HTML support
Updates golang/go#20587
Change-Id: Ie4ba1a4c31d363310b654bb6dff5080b0528cb32
Reviewed-on: https://go-review.googlesource.com/45011
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/cmd/relnote/relnote.go b/cmd/relnote/relnote.go
index cf3633d..db771be 100644
--- a/cmd/relnote/relnote.go
+++ b/cmd/relnote/relnote.go
@@ -7,8 +7,12 @@
package main
import (
+ "bytes"
"context"
+ "flag"
"fmt"
+ "html"
+ "io/ioutil"
"log"
"regexp"
"sort"
@@ -18,52 +22,127 @@
"golang.org/x/build/maintner/godata"
)
+var (
+ htmlMode = flag.Bool("html", false, "write HTML output")
+ exclFile = flag.String("exclude-from", "", "optional path to release notes HTML 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 was noted via a RELNOTE= comment.
+type change struct {
+ CL *maintner.GerritCL
+ Note string // the part after RELNOTE=
+}
+
+func (c change) TextLine() string {
+ subj := clSubject(c.CL)
+ if c.Note != "yes" && c.Note != "y" {
+ subj = c.Note + ": " + subj
+ }
+ return fmt.Sprintf("https://golang.org/cl/%d: %s", c.CL.Number, subj)
+}
+
func main() {
+ flag.Parse()
+
+ var existingHTML []byte
+ if *exclFile != "" {
+ var err error
+ existingHTML, err = ioutil.ReadFile(*exclFile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+
corpus, err := godata.Get(context.Background())
if err != nil {
log.Fatal(err)
}
ger := corpus.Gerrit()
- changes := map[string][]string{} // keyed by pkg
+ changes := map[string][]change{} // keyed by pkg
ger.ForeachProjectUnsorted(func(gp *maintner.GerritProject) error {
if gp.Server() != "go.googlesource.com" {
return nil
}
gp.ForeachCLUnsorted(func(cl *maintner.GerritCL) error {
- if relnote := clRelNote(cl); relnote != "" {
- subj := cl.Commit.Msg
- if i := strings.Index(subj, "\n"); i != -1 {
- subj = subj[:i]
- }
- pkg := "??"
- if i := strings.Index(subj, ":"); i != -1 {
- pkg = subj[:i]
- }
- if relnote != "yes" {
- subj = relnote + ": " + subj
- }
- change := fmt.Sprintf("https://golang.org/cl/%d: %s", cl.Number, subj)
- changes[pkg] = append(changes[pkg], change)
+ relnote := clRelNote(cl)
+ if relnote == "" ||
+ bytes.Contains(existingHTML, []byte(fmt.Sprintf("CL %d", cl.Number))) {
+ return nil
}
+ pkg := clPackage(cl)
+ changes[pkg] = append(changes[pkg], change{
+ Note: relnote,
+ CL: cl,
+ })
return nil
})
return nil
})
var pkgs []string
- for pkg, lines := range changes {
+ for pkg, changes := range changes {
pkgs = append(pkgs, pkg)
- sort.Strings(lines)
+ sort.Slice(changes, func(i, j int) bool {
+ return changes[i].CL.Number < changes[j].CL.Number
+ })
}
sort.Strings(pkgs)
- for _, pkg := range pkgs {
- fmt.Printf("%s\n", pkg)
- for _, change := range changes[pkg] {
- fmt.Printf(" %s\n", change)
+
+ if *htmlMode {
+ for _, pkg := range pkgs {
+ if !strings.HasPrefix(pkg, "cmd/") {
+ continue
+ }
+ for _, change := range changes[pkg] {
+ fmt.Printf("<!-- CL %d: %s -->\n", change.CL.Number, change.TextLine())
+ }
+ }
+ for _, pkg := range pkgs {
+ if strings.HasPrefix(pkg, "cmd/") {
+ continue
+ }
+ fmt.Printf("<dl id=%q><dt><a href=%q>%s</a></dt>\n <dd>\n",
+ pkg, "/pkg/"+pkg+"/", pkg)
+ for _, change := range changes[pkg] {
+ changeURL := fmt.Sprintf("https://golang.org/cl/%d", change.CL.Number)
+ subj := clSubject(change.CL)
+ subj = strings.TrimPrefix(subj, pkg+": ")
+ fmt.Printf(" <p><!-- CL %d -->\n TODO: <a href=%q>%s</a>: %s\n </p>\n\n",
+ change.CL.Number, changeURL, changeURL, html.EscapeString(subj))
+ }
+ fmt.Printf("</dl><!-- %s -->\n\n", pkg)
+ }
+
+ } else {
+ for _, pkg := range pkgs {
+ fmt.Printf("%s\n", pkg)
+ for _, change := range changes[pkg] {
+ fmt.Printf(" %s\n", change.TextLine())
+ }
}
}
}
+// clSubject returns the first line of the CL's commit message,
+// without the trailing newline.
+func clSubject(cl *maintner.GerritCL) string {
+ subj := cl.Commit.Msg
+ if i := strings.Index(subj, "\n"); i != -1 {
+ return subj[:i]
+ }
+ return subj
+}
+
+// clPackage returns the package name from the CL's commit message,
+// or "??" if it's formatted unconventionally.
+func clPackage(cl *maintner.GerritCL) string {
+ subj := clSubject(cl)
+ if i := strings.Index(subj, ":"); i != -1 {
+ return subj[:i]
+ }
+ return "??"
+}
+
var relNoteRx = regexp.MustCompile(`RELNOTES?=(.+)`)
func parseRelNote(s string) string {