@@ -31,37 +31,41 @@ type ToDo struct {
31
31
// It takes the doc/next directory of the repo and the date of the last release.
32
32
func todo (w io.Writer , fsys fs.FS , prevRelDate time.Time ) error {
33
33
var todos []ToDo
34
+ addToDo := func (td ToDo ) { todos = append (todos , td ) }
34
35
35
- add := func (td ToDo ) { todos = append (todos , td ) }
36
+ mentionedIssues := map [int ]bool {} // issues mentioned in the existing relnotes
37
+ addIssue := func (num int ) { mentionedIssues [num ] = true }
36
38
37
- if err := todosFromDocFiles (fsys , add ); err != nil {
39
+ if err := infoFromDocFiles (fsys , addToDo , addIssue ); err != nil {
38
40
return err
39
41
}
40
42
if ! prevRelDate .IsZero () {
41
- if err := todosFromCLs (prevRelDate , add ); err != nil {
43
+ if err := todosFromCLs (prevRelDate , mentionedIssues , addToDo ); err != nil {
42
44
return err
43
45
}
44
46
}
45
47
return writeToDos (w , todos )
46
48
}
47
49
48
- // Collect TODOs from the markdown files in the main repo.
49
- func todosFromDocFiles (fsys fs.FS , add func (ToDo )) error {
50
+ // Collect TODOs and issue numbers from the markdown files in the main repo.
51
+ func infoFromDocFiles (fsys fs.FS , addToDo func (ToDo ), addIssue func ( int )) error {
50
52
// This is essentially a grep.
51
53
return fs .WalkDir (fsys , "." , func (path string , d fs.DirEntry , err error ) error {
52
54
if err != nil {
53
55
return err
54
56
}
55
57
if ! d .IsDir () && strings .HasSuffix (path , ".md" ) {
56
- if err := todosFromFile (fsys , path , add ); err != nil {
58
+ if err := infoFromFile (fsys , path , addToDo , addIssue ); err != nil {
57
59
return err
58
60
}
59
61
}
60
62
return nil
61
63
})
62
64
}
63
65
64
- func todosFromFile (dir fs.FS , filename string , add func (ToDo )) error {
66
+ var issueRE = regexp .MustCompile ("/issue/([0-9]+)" )
67
+
68
+ func infoFromFile (dir fs.FS , filename string , addToDo func (ToDo ), addIssue func (int )) error {
65
69
f , err := dir .Open (filename )
66
70
if err != nil {
67
71
return err
@@ -71,17 +75,25 @@ func todosFromFile(dir fs.FS, filename string, add func(ToDo)) error {
71
75
ln := 0
72
76
for scan .Scan () {
73
77
ln ++
74
- if line := scan .Text (); strings .Contains (line , "TODO" ) {
75
- add (ToDo {
78
+ line := scan .Text ()
79
+ if strings .Contains (line , "TODO" ) {
80
+ addToDo (ToDo {
76
81
message : line ,
77
82
provenance : fmt .Sprintf ("%s:%d" , filename , ln ),
78
83
})
79
84
}
85
+ for _ , matches := range issueRE .FindAllStringSubmatch (line , - 1 ) {
86
+ num , err := strconv .Atoi (matches [1 ])
87
+ if err != nil {
88
+ return fmt .Errorf ("%s:%d: %v" , filename , ln , err )
89
+ }
90
+ addIssue (num )
91
+ }
80
92
}
81
93
return scan .Err ()
82
94
}
83
95
84
- func todosFromCLs (cutoff time.Time , add func (ToDo )) error {
96
+ func todosFromCLs (cutoff time.Time , mentionedIssues map [ int ] bool , add func (ToDo )) error {
85
97
ctx := context .Background ()
86
98
// The maintner corpus doesn't track inline comments. See go.dev/issue/24863.
87
99
// So we need to use a Gerrit API client to fetch them instead. If maintner starts
@@ -120,7 +132,7 @@ func todosFromCLs(cutoff time.Time, add func(ToDo)) error {
120
132
}
121
133
}
122
134
// Add a TODO if the CL refers to an accepted proposal.
123
- todoFromProposal (ctx , cl , gh , add )
135
+ todoFromProposal (cl , gh , mentionedIssues , add )
124
136
return nil
125
137
})
126
138
})
@@ -143,11 +155,12 @@ func todoFromRelnote(ctx context.Context, cl *maintner.GerritCL, gc *gerrit.Clie
143
155
return nil
144
156
}
145
157
146
- func todoFromProposal (ctx context. Context , cl * maintner.GerritCL , gh * maintner.GitHubRepo , add func (ToDo )) {
158
+ func todoFromProposal (cl * maintner.GerritCL , gh * maintner.GitHubRepo , mentionedIssues map [ int ] bool , add func (ToDo )) {
147
159
for _ , num := range issueNumbers (cl ) {
148
- // TODO(jba): look for CL references in existing release notes to avoid adding TODOs for
149
- // CLs that have already been documented.
150
- if issue := gh .Issue (num ); issue != nil && hasLabel (issue , "Proposal-Accepted" ) {
160
+ if mentionedIssues [num ] {
161
+ continue
162
+ }
163
+ if issue := gh .Issue (int32 (num )); issue != nil && hasLabel (issue , "Proposal-Accepted" ) {
151
164
// Add a TODO for all issues, regardless of when or whether they are closed.
152
165
// Any work on an accepted proposal is potentially worthy of a release note.
153
166
add (ToDo {
@@ -223,18 +236,18 @@ var numbersRE = regexp.MustCompile(`(?m)(?:^|\s|golang/go)#([0-9]{3,})`)
223
236
var golangGoNumbersRE = regexp .MustCompile (`(?m)golang/go#([0-9]{3,})` )
224
237
225
238
// issueNumbers returns the golang/go issue numbers referred to by the CL.
226
- func issueNumbers (cl * maintner.GerritCL ) []int32 {
239
+ func issueNumbers (cl * maintner.GerritCL ) []int {
227
240
var re * regexp.Regexp
228
241
if cl .Project .Project () == "go" {
229
242
re = numbersRE
230
243
} else {
231
244
re = golangGoNumbersRE
232
245
}
233
246
234
- var list []int32
247
+ var list []int
235
248
for _ , s := range re .FindAllStringSubmatch (cl .Commit .Msg , - 1 ) {
236
249
if n , err := strconv .Atoi (s [1 ]); err == nil && n < 1e9 {
237
- list = append (list , int32 ( n ) )
250
+ list = append (list , n )
238
251
}
239
252
}
240
253
// Remove duplicates.
0 commit comments