@@ -10,6 +10,10 @@ import (
10
10
"fmt"
11
11
"io"
12
12
"io/fs"
13
+ "log"
14
+ "os"
15
+ "os/exec"
16
+ "path/filepath"
13
17
"regexp"
14
18
"slices"
15
19
"strconv"
@@ -29,24 +33,59 @@ type ToDo struct {
29
33
30
34
// todo prints a report to w on which release notes need to be written.
31
35
// It takes the doc/next directory of the repo and the date of the last release.
32
- func todo (w io.Writer , fsys fs.FS , prevRelDate time.Time ) error {
36
+ func todo (w io.Writer , goroot string , treeOpenDate time.Time ) error {
37
+ // If not provided, determine when the tree was opened by looking
38
+ // at when the version file was updated.
39
+ if treeOpenDate .IsZero () {
40
+ var err error
41
+ treeOpenDate , err = findTreeOpenDate (goroot )
42
+ if err != nil {
43
+ return err
44
+ }
45
+ }
46
+ log .Printf ("collecting TODOs from %s since %s" , goroot , treeOpenDate .Format (time .DateOnly ))
47
+
33
48
var todos []ToDo
34
49
addToDo := func (td ToDo ) { todos = append (todos , td ) }
35
50
36
51
mentionedIssues := map [int ]bool {} // issues mentioned in the existing relnotes
37
52
addIssue := func (num int ) { mentionedIssues [num ] = true }
38
53
39
- if err := infoFromDocFiles (fsys , addToDo , addIssue ); err != nil {
54
+ nextDir := filepath .Join (goroot , "doc" , "next" )
55
+ if err := infoFromDocFiles (os .DirFS (nextDir ), addToDo , addIssue ); err != nil {
40
56
return err
41
57
}
42
- if ! prevRelDate .IsZero () {
43
- if err := todosFromCLs (prevRelDate , mentionedIssues , addToDo ); err != nil {
44
- return err
45
- }
58
+ if err := todosFromCLs (treeOpenDate , mentionedIssues , addToDo ); err != nil {
59
+ return err
46
60
}
47
61
return writeToDos (w , todos )
48
62
}
49
63
64
+ // findTreeOpenDate returns the time of the most recent commit to the file that
65
+ // determines the version of Go under development.
66
+ func findTreeOpenDate (goroot string ) (time.Time , error ) {
67
+ versionFilePath := filepath .FromSlash ("src/internal/goversion/goversion.go" )
68
+ if _ , err := exec .LookPath ("git" ); err != nil {
69
+ return time.Time {}, fmt .Errorf ("looking for git binary: %v" , err )
70
+ }
71
+ // List the most recent commit to versionFilePath, displaying the date and subject.
72
+ outb , err := exec .Command ("git" , "-C" , goroot , "log" , "-n" , "1" ,
73
+ "--format=%cs %s" , "--" , versionFilePath ).Output ()
74
+ if err != nil {
75
+ return time.Time {}, err
76
+ }
77
+ out := string (outb )
78
+ // The commit messages follow a standard form. Check for the right words to avoid mistakenly
79
+ // choosing the wrong commit.
80
+ const updateString = "update version to"
81
+ if ! strings .Contains (strings .ToLower (out ), updateString ) {
82
+ return time.Time {}, fmt .Errorf ("cannot determine tree-open date: most recent commit for %s does not contain %q" ,
83
+ versionFilePath , updateString )
84
+ }
85
+ dateString , _ , _ := strings .Cut (out , " " )
86
+ return time .Parse (time .DateOnly , dateString )
87
+ }
88
+
50
89
// Collect TODOs and issue numbers from the markdown files in the main repo.
51
90
func infoFromDocFiles (fsys fs.FS , addToDo func (ToDo ), addIssue func (int )) error {
52
91
// This is essentially a grep.
0 commit comments