Skip to content

Commit

Permalink
cmd/relnote: find cutoff date automatically
Browse files Browse the repository at this point in the history
Look for the date of CL that opened the tree to find the
cutoff for TODOs.

Add a flag for the date in case that doesn't work.

For golang/go#64169.

Change-Id: I756e5622339f5e1963c39b8e0bbd7eeb3fc23d85
Reviewed-on: https://go-review.googlesource.com/c/build/+/584401
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Russ Cox <rsc@golang.org>
  • Loading branch information
jba committed May 10, 2024
1 parent c639adb commit 4b28576
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 30 deletions.
27 changes: 13 additions & 14 deletions cmd/relnote/relnote.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,18 @@ import (
)

var (
verbose = flag.Bool("v", false, "print verbose logging")
goroot = flag.String("goroot", runtime.GOROOT(), "root of Go repo containing docs")
verbose = flag.Bool("v", false, "print verbose logging")
goroot = flag.String("goroot", runtime.GOROOT(), "root of Go repo containing docs")
todosSince = flag.String("since", "", "earliest to look for TODOs, in YYYY-MM-DD format")
)

func usage() {
out := flag.CommandLine.Output()
fmt.Fprintf(out, "usage:\n")
fmt.Fprintf(out, " relnote generate\n")
fmt.Fprintf(out, " generate release notes from doc/next\n")
fmt.Fprintf(out, " relnote todo PREVIOUS_RELEASE_DATE\n")
fmt.Fprintf(out, " report which release notes need to be written; use YYYY-MM-DD format for date of last release\n")
fmt.Fprintf(out, " relnote todo\n")
fmt.Fprintf(out, " report which release notes need to be written\n")
flag.PrintDefaults()
}

Expand All @@ -54,18 +55,16 @@ func main() {
if cmd := flag.Arg(0); cmd != "" {
switch cmd {
case "generate":
err = generate(version, flag.Arg(1))
err = generate(version, *goroot)
case "todo":
prevDate := flag.Arg(1)
if prevDate == "" {
log.Fatal("need previous release date")
var sinceDate time.Time
if *todosSince != "" {
sinceDate, err = time.Parse(time.DateOnly, *todosSince)
if err != nil {
log.Fatalf("-since flag: %v", err)
}
}
prevDateTime, err := time.Parse("2006-01-02", prevDate)
if err != nil {
log.Fatalf("previous release date: %s", err)
}
nextDir := filepath.Join(*goroot, "doc", "next")
err = todo(os.Stdout, os.DirFS(nextDir), prevDateTime)
err = todo(os.Stdout, *goroot, sinceDate)
default:
err = fmt.Errorf("unknown command %q", cmd)
}
Expand Down
51 changes: 45 additions & 6 deletions cmd/relnote/todo.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
"fmt"
"io"
"io/fs"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strconv"
Expand All @@ -29,24 +33,59 @@ type ToDo struct {

// todo prints a report to w on which release notes need to be written.
// It takes the doc/next directory of the repo and the date of the last release.
func todo(w io.Writer, fsys fs.FS, prevRelDate time.Time) error {
func todo(w io.Writer, goroot string, treeOpenDate time.Time) error {
// If not provided, determine when the tree was opened by looking
// at when the version file was updated.
if treeOpenDate.IsZero() {
var err error
treeOpenDate, err = findTreeOpenDate(goroot)
if err != nil {
return err
}
}
log.Printf("collecting TODOs from %s since %s", goroot, treeOpenDate.Format(time.DateOnly))

var todos []ToDo
addToDo := func(td ToDo) { todos = append(todos, td) }

mentionedIssues := map[int]bool{} // issues mentioned in the existing relnotes
addIssue := func(num int) { mentionedIssues[num] = true }

if err := infoFromDocFiles(fsys, addToDo, addIssue); err != nil {
nextDir := filepath.Join(goroot, "doc", "next")
if err := infoFromDocFiles(os.DirFS(nextDir), addToDo, addIssue); err != nil {
return err
}
if !prevRelDate.IsZero() {
if err := todosFromCLs(prevRelDate, mentionedIssues, addToDo); err != nil {
return err
}
if err := todosFromCLs(treeOpenDate, mentionedIssues, addToDo); err != nil {
return err
}
return writeToDos(w, todos)
}

// findTreeOpenDate returns the time of the most recent commit to the file that
// determines the version of Go under development.
func findTreeOpenDate(goroot string) (time.Time, error) {
versionFilePath := filepath.FromSlash("src/internal/goversion/goversion.go")
if _, err := exec.LookPath("git"); err != nil {
return time.Time{}, fmt.Errorf("looking for git binary: %v", err)
}
// List the most recent commit to versionFilePath, displaying the date and subject.
outb, err := exec.Command("git", "-C", goroot, "log", "-n", "1",
"--format=%cs %s", "--", versionFilePath).Output()
if err != nil {
return time.Time{}, err
}
out := string(outb)
// The commit messages follow a standard form. Check for the right words to avoid mistakenly
// choosing the wrong commit.
const updateString = "update version to"
if !strings.Contains(strings.ToLower(out), updateString) {
return time.Time{}, fmt.Errorf("cannot determine tree-open date: most recent commit for %s does not contain %q",
versionFilePath, updateString)
}
dateString, _, _ := strings.Cut(out, " ")
return time.Parse(time.DateOnly, dateString)
}

// Collect TODOs and issue numbers from the markdown files in the main repo.
func infoFromDocFiles(fsys fs.FS, addToDo func(ToDo), addIssue func(int)) error {
// This is essentially a grep.
Expand Down
25 changes: 15 additions & 10 deletions cmd/relnote/todo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
package main

import (
"bytes"
"slices"
"testing"
"testing/fstest"
"time"
)

func TestToDo(t *testing.T) {
func TestInfoFromDocFiles(t *testing.T) {
files := map[string]string{
"a.md": "TODO: write something",
"b.md": "nothing to do",
Expand All @@ -22,14 +21,20 @@ func TestToDo(t *testing.T) {
for name, contents := range files {
dir[name] = &fstest.MapFile{Data: []byte(contents)}
}
var buf bytes.Buffer
if err := todo(&buf, dir, time.Time{}); err != nil {
var got []ToDo
addToDo := func(td ToDo) { got = append(got, td) }
addIssue := func(int) {}
if err := infoFromDocFiles(dir, addToDo, addIssue); err != nil {
t.Fatal(err)
}
got := buf.String()
want := `TODO: write something (from a.md:1)
`
if got != want {
t.Errorf("\ngot:\n%s\nwant:\n%s", got, want)
want := []ToDo{
{
message: "TODO: write something",
provenance: "a.md:1",
},
}

if !slices.Equal(got, want) {
t.Errorf("\ngot:\n%+v\nwant:\n%+v", got, want)
}
}

0 comments on commit 4b28576

Please sign in to comment.