cmd/cvetriage,internal/cvelist: add option to read local repo

An option is added to read from a local copy of
github.com/CVEProject/cvelist, instead of cloning the repo, to make it
faster to run cmd/cvetriage.

Change-Id: I4889c6de224b4e423fef94b66d6873df29bff58d
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/360839
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/cmd/cvetriage/main.go b/cmd/cvetriage/main.go
index 0be2209..89530c3 100644
--- a/cmd/cvetriage/main.go
+++ b/cmd/cvetriage/main.go
@@ -17,6 +17,7 @@
 package main
 
 import (
+	"flag"
 	"fmt"
 	"log"
 	"strings"
@@ -27,17 +28,28 @@
 )
 
 func main() {
-	if err := run(); err != nil {
+	flag.Parse()
+	args := flag.Args()
+
+	var repoPath string
+	switch len(args) {
+	case 0:
+	case 1:
+		repoPath = args[0]
+	default:
+		log.Fatalf("unexpected number of args: %v", args)
+	}
+	if err := run(repoPath); err != nil {
 		log.Fatal(err)
 	}
 }
 
-func run() (err error) {
+func run(repoPath string) (err error) {
 	triaged, err := readTriagedCVEList()
 	if err != nil {
 		return err
 	}
-	return worker.Run(triaged)
+	return worker.Run(repoPath, triaged)
 }
 
 const (
diff --git a/internal/worker/worker.go b/internal/worker/worker.go
index 4f036ca..dd75557 100644
--- a/internal/worker/worker.go
+++ b/internal/worker/worker.go
@@ -27,10 +27,18 @@
 
 // Run clones the CVEProject/cvelist repository and compares the files to the
 // existing triaged-cve-list.
-func Run(triaged map[string]bool) (err error) {
+func Run(dirpath string, triaged map[string]bool) (err error) {
 	defer derrors.Wrap(&err, "Run(triaged)")
-	log.Printf("Cloning %q...", cvelistRepoURL)
-	repo, root, err := cloneRepo(cvelistRepoURL)
+	var repo *git.Repository
+	if dirpath != "" {
+		repo, err = openRepo(dirpath)
+	} else {
+		repo, err = cloneRepo(cvelistRepoURL)
+	}
+	if err != nil {
+		return err
+	}
+	root, err := getRepoRoot(repo)
 	if err != nil {
 		return err
 	}
@@ -44,32 +52,43 @@
 
 // cloneRepo returns a repo and tree object for the repo at HEAD by
 // cloning the repo at repoURL.
-func cloneRepo(repoURL string) (repo *git.Repository, root *object.Tree, err error) {
+func cloneRepo(repoURL string) (repo *git.Repository, err error) {
 	defer derrors.Wrap(&err, "cloneRepo(%q)", repoURL)
-	repo, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
+	log.Printf("Cloning %q...", cvelistRepoURL)
+	return git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
 		URL:           repoURL,
 		ReferenceName: plumbing.HEAD,
 		SingleBranch:  true,
 		Depth:         1,
 		Tags:          git.NoTags,
 	})
+}
+
+func openRepo(dirpath string) (repo *git.Repository, err error) {
+	defer derrors.Wrap(&err, "openRepo(%q)", dirpath)
+	log.Printf("Opening %q...", dirpath)
+	repo, err = git.PlainOpen(dirpath)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
+	return repo, nil
+}
+
+func getRepoRoot(repo *git.Repository) (root *object.Tree, err error) {
 	refName := plumbing.HEAD
 	ref, err := repo.Reference(refName, true)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 	commit, err := repo.CommitObject(ref.Hash())
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 	root, err = repo.TreeObject(commit.TreeHash)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
-	return repo, root, nil
+	return root, nil
 }
 
 // createIssuesToTriage creates GitHub issues to be triaged by the Go security