Skip to content

Commit ab83534

Browse files
dmitshurgopherbot
authored andcommitted
cmd/relnote: improve tracking of mentioned issues and CLs
Make the output of 'relnote todo' more useful: - consider the existence of a *-stdlib/*-minor/.../nnn.md file to be equivalent to an explicit mention of an issue nnn; this way there's no need to enforce that a file like 12345.md must also have a comment like <!-- go.dev/issue/12345 --> - improve the TODO wording of legacy RELNOTE= Gerrit comments when the release note isn't specified (beyond just "y" or "yes") - track CLs that are mentioned (e.g., "CL nnn") and consider such mentions to mark legacy RELNOTE= Gerrit comments as handled - sort provenances to remove a remaining source of non-deterministic output With these changes, relnote todo | wc -l goes from 52 lines down to 8, removing many mentions of proposals that are mentioned in nnn.md files without an explicit comment, and a few legacy RELNOTE= Gerrit comments that were reviewed as well. For golang/go#64169. Change-Id: If285c431854d279dd7dba6c145ba696ec003b351 Reviewed-on: https://go-review.googlesource.com/c/build/+/591317 Auto-Submit: Dmitri Shuralyov <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent e41b086 commit ab83534

File tree

3 files changed

+45
-19
lines changed

3 files changed

+45
-19
lines changed

cmd/relnote/relnote.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ var (
2727
func usage() {
2828
out := flag.CommandLine.Output()
2929
fmt.Fprintf(out, "usage:\n")
30-
fmt.Fprintf(out, " relnote generate\n")
30+
fmt.Fprintf(out, " relnote [flags] generate\n")
3131
fmt.Fprintf(out, " generate release notes from doc/next\n")
32-
fmt.Fprintf(out, " relnote todo\n")
32+
fmt.Fprintf(out, " relnote [flags] todo\n")
3333
fmt.Fprintf(out, " report which release notes need to be written\n")
34+
fmt.Fprintln(out)
3435
flag.PrintDefaults()
3536
}
3637

cmd/relnote/todo.go

+41-15
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,26 @@ func todo(w io.Writer, goroot string, treeOpenDate time.Time) error {
4848
var todos []ToDo
4949
addToDo := func(td ToDo) { todos = append(todos, td) }
5050

51-
mentionedIssues := map[int]bool{} // issues mentioned in the existing relnotes
52-
addIssue := func(num int) { mentionedIssues[num] = true }
53-
51+
mentioned := mentioned{Issues: make(map[int]bool), CLs: make(map[int]bool)}
5452
nextDir := filepath.Join(goroot, "doc", "next")
55-
if err := infoFromDocFiles(os.DirFS(nextDir), addToDo, addIssue); err != nil {
53+
if err := infoFromDocFiles(os.DirFS(nextDir), mentioned, addToDo); err != nil {
5654
return err
5755
}
58-
if err := todosFromCLs(treeOpenDate, mentionedIssues, addToDo); err != nil {
56+
if err := todosFromCLs(treeOpenDate, mentioned, addToDo); err != nil {
5957
return err
6058
}
6159
return writeToDos(w, todos)
6260
}
6361

62+
// mentioned collects mentions within the existing relnotes.
63+
type mentioned struct {
64+
Issues map[int]bool
65+
CLs map[int]bool
66+
}
67+
68+
func (m mentioned) AddIssue(num int) { m.Issues[num] = true }
69+
func (m mentioned) AddCL(num int) { m.CLs[num] = true }
70+
6471
// findTreeOpenDate returns the time of the most recent commit to the file that
6572
// determines the version of Go under development.
6673
func findTreeOpenDate(goroot string) (time.Time, error) {
@@ -87,24 +94,35 @@ func findTreeOpenDate(goroot string) (time.Time, error) {
8794
}
8895

8996
// Collect TODOs and issue numbers from the markdown files in the main repo.
90-
func infoFromDocFiles(fsys fs.FS, addToDo func(ToDo), addIssue func(int)) error {
97+
func infoFromDocFiles(fsys fs.FS, mentioned mentioned, addToDo func(ToDo)) error {
9198
// This is essentially a grep.
9299
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
93100
if err != nil {
94101
return err
95102
}
96103
if !d.IsDir() && strings.HasSuffix(path, ".md") {
97-
if err := infoFromFile(fsys, path, addToDo, addIssue); err != nil {
104+
if err := infoFromFile(fsys, path, mentioned, addToDo); err != nil {
98105
return err
99106
}
100107
}
101108
return nil
102109
})
103110
}
104111

105-
var issueRE = regexp.MustCompile("/issue/([0-9]+)")
112+
var (
113+
issueRE = regexp.MustCompile("/issue/([1-9][0-9]*)")
114+
issueViaFilenameRE = regexp.MustCompile(`^\d+-stdlib/\d+-minor/.+/([1-9][0-9]*).md$`)
115+
clRE = regexp.MustCompile("CL ([1-9][0-9]*)")
116+
)
106117

107-
func infoFromFile(dir fs.FS, filename string, addToDo func(ToDo), addIssue func(int)) error {
118+
func infoFromFile(dir fs.FS, filename string, mentioned mentioned, addToDo func(ToDo)) error {
119+
if matches := issueViaFilenameRE.FindStringSubmatch(filename); matches != nil {
120+
num, err := strconv.Atoi(matches[1])
121+
if err != nil {
122+
return fmt.Errorf("%s: %v", filename, err)
123+
}
124+
mentioned.AddIssue(num)
125+
}
108126
f, err := dir.Open(filename)
109127
if err != nil {
110128
return err
@@ -126,13 +144,20 @@ func infoFromFile(dir fs.FS, filename string, addToDo func(ToDo), addIssue func(
126144
if err != nil {
127145
return fmt.Errorf("%s:%d: %v", filename, ln, err)
128146
}
129-
addIssue(num)
147+
mentioned.AddIssue(num)
148+
}
149+
for _, matches := range clRE.FindAllStringSubmatch(line, -1) {
150+
num, err := strconv.Atoi(matches[1])
151+
if err != nil {
152+
return fmt.Errorf("%s:%d: %v", filename, ln, err)
153+
}
154+
mentioned.AddCL(num)
130155
}
131156
}
132157
return scan.Err()
133158
}
134159

135-
func todosFromCLs(cutoff time.Time, mentionedIssues map[int]bool, add func(ToDo)) error {
160+
func todosFromCLs(cutoff time.Time, mentioned mentioned, add func(ToDo)) error {
136161
ctx := context.Background()
137162
// The maintner corpus doesn't track inline comments. See go.dev/issue/24863.
138163
// So we need to use a Gerrit API client to fetch them instead. If maintner starts
@@ -165,13 +190,13 @@ func todosFromCLs(cutoff time.Time, mentionedIssues map[int]bool, add func(ToDo)
165190
}
166191
// Add a TODO if the CL has a "RELNOTE=" comment.
167192
// These are deprecated, but we look for them just in case.
168-
if _, ok := matchedCLs[int(cl.Number)]; ok {
193+
if _, ok := matchedCLs[int(cl.Number)]; ok && !mentioned.CLs[int(cl.Number)] {
169194
if err := todoFromRelnote(ctx, cl, gerritClient, add); err != nil {
170195
return err
171196
}
172197
}
173198
// Add a TODO if the CL refers to an accepted proposal.
174-
todoFromProposal(cl, gh, mentionedIssues, add)
199+
todoFromProposal(cl, gh, mentioned.Issues, add)
175200
return nil
176201
})
177202
})
@@ -184,10 +209,10 @@ func todoFromRelnote(ctx context.Context, cl *maintner.GerritCL, gc *gerrit.Clie
184209
}
185210
if rn := clRelNote(cl, comments); rn != "" {
186211
if rn == "yes" || rn == "y" {
187-
rn = "UNKNOWN"
212+
rn = fmt.Sprintf("CL %d has a RELNOTE comment without a suggested text", cl.Number)
188213
}
189214
add(ToDo{
190-
message: "TODO:" + rn,
215+
message: "TODO: " + rn,
191216
provenance: fmt.Sprintf("RELNOTE comment in https://go.dev/cl/%d", cl.Number),
192217
})
193218
}
@@ -308,6 +333,7 @@ func writeToDos(w io.Writer, todos []ToDo) error {
308333
for _, td := range byMessage[msg] {
309334
provs = append(provs, td.provenance)
310335
}
336+
slices.Sort(provs) // for deterministic output
311337
if _, err := fmt.Fprintf(w, "%s (from %s)\n", msg, strings.Join(provs, ", ")); err != nil {
312338
return err
313339
}

cmd/relnote/todo_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ func TestInfoFromDocFiles(t *testing.T) {
2323
}
2424
var got []ToDo
2525
addToDo := func(td ToDo) { got = append(got, td) }
26-
addIssue := func(int) {}
27-
if err := infoFromDocFiles(dir, addToDo, addIssue); err != nil {
26+
if err := infoFromDocFiles(dir, mentioned{}, addToDo); err != nil {
2827
t.Fatal(err)
2928
}
3029
want := []ToDo{

0 commit comments

Comments
 (0)