cmd/vulnreport: add commit command
The "vulnreport commit reports/GO-XXXX-YYYY.yaml" command runs
"vulnreport fix" on the file and then commits it with a standard
commit message if there are no remaining lint warnings.
Simplifies the workflow of adding new reports by taking responsibility
for setting the commit message, and ensuring fix/lint have been run.
Change-Id: I845ca622c1e53789670c3a6a7e192c6fa2ffcebf
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/412415
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Julie Qiu <julieqiu@google.com>
diff --git a/cmd/vulnreport/main.go b/cmd/vulnreport/main.go
index 050d4c6..9e57331 100644
--- a/cmd/vulnreport/main.go
+++ b/cmd/vulnreport/main.go
@@ -15,11 +15,13 @@
"go/build"
"log"
"os"
+ "regexp"
"sort"
"strconv"
"strings"
"time"
+ "github.com/go-git/go-git/v5"
"golang.org/x/tools/go/packages"
"golang.org/x/vulndb/internal/cvelistrepo"
"golang.org/x/vulndb/internal/derrors"
@@ -82,6 +84,15 @@
if err := multi(lint, names); err != nil {
log.Fatal(err)
}
+ case "commit":
+ repo, err := gitrepo.Open(ctx, ".")
+ if err != nil {
+ log.Fatal(err)
+ }
+ f := func(name string) error { return commit(ctx, repo, name, *githubToken) }
+ if err := multi(f, names); err != nil {
+ log.Fatal(err)
+ }
case "newcve":
if err := multi(newCVE, names); err != nil {
log.Fatal(err)
@@ -306,6 +317,83 @@
return newslice, nil
}
+var reportRegexp = regexp.MustCompile(`^reports/GO-\d\d\d\d-(\d+)\.yaml$`)
+
+func commit(ctx context.Context, repo *git.Repository, filename, accessToken string) (err error) {
+ defer derrors.Wrap(&err, "commit(%q)", filename)
+ m := reportRegexp.FindStringSubmatch(filename)
+ if len(m) != 2 {
+ return fmt.Errorf("%v: not a report filename", filename)
+ }
+ issueID := m[1]
+
+ // Ignore errors. If anything is really wrong with the report, we'll
+ // detect it on re-linting below.
+ _ = fix(ctx, filename, accessToken)
+
+ r, err := report.Read(filename)
+ if err != nil {
+ return err
+ }
+ if lints := r.Lint(); len(lints) > 0 {
+ fmt.Fprintf(os.Stderr, "%v: contains lint warnings, not committing\n", filename)
+ for _, l := range lints {
+ fmt.Fprintln(os.Stderr, l)
+ }
+ fmt.Fprintln(os.Stderr)
+ return nil
+ }
+
+ tree, err := repo.Worktree()
+ if err != nil {
+ return err
+ }
+ _, err = tree.Add(filename)
+ if err != nil {
+ return err
+ }
+ st, err := tree.Status()
+ if err != nil {
+ return err
+ }
+ if _, ok := st[filename]; !ok {
+ // Trying to commit a file that hasn't changed from HEAD.
+ fmt.Printf("%v: unmodified\n", filename)
+ return nil
+ }
+ msg := fmt.Sprintf("x/vulndb: add %v for %v\n\nFixes golang/vulndb#%v\n",
+ filename, strings.Join(r.CVEs, ", "), issueID)
+ _, err = tree.Commit(msg, &git.CommitOptions{})
+ return err
+}
+
+// Regexp for matching go tags. The groups are:
+// 1 the major.minor version
+// 2 the patch version, or empty if none
+// 3 the entire prerelease, if present
+// 4 the prerelease type ("beta" or "rc")
+// 5 the prerelease number
+var tagRegexp = regexp.MustCompile(`^go(\d+\.\d+)(\.\d+|)((beta|rc)(\d+))?$`)
+
+// versionForTag returns the semantic version for a Go version string,
+// or "" if the version string doesn't correspond to a Go release or beta.
+func semverForGoVersion(v string) report.Version {
+ m := tagRegexp.FindStringSubmatch(v)
+ if m == nil {
+ return ""
+ }
+ version := m[1]
+ if m[2] != "" {
+ version += m[2]
+ } else {
+ version += ".0"
+ }
+ if m[3] != "" {
+ version += "-" + m[4] + "." + m[5]
+ }
+ return report.Version(version)
+}
+
// loadPackage loads the package at the given import path, with enough
// information for constructing a call graph.
func loadPackage(cfg *packages.Config, importPath string) ([]*packages.Package, error) {