maintner: start of git polling (just proto and maintner plumbing, no git)

This is just the start of git commit metadata importing, but without
any of the git-specific bits. This is just the proto changes and the
changes to wire up the guts with maintner. The guts will come in a
following CL.

Change-Id: I321590dc30ba5c62346911526fed647fc0d0b3ad
Reviewed-on: https://go-review.googlesource.com/37938
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/maintner/maintner.go b/maintner/maintner.go
index 993c128..1b26cfa 100644
--- a/maintner/maintner.go
+++ b/maintner/maintner.go
@@ -16,6 +16,7 @@
 	"fmt"
 	"io/ioutil"
 	"log"
+	"regexp"
 	"strings"
 	"sync"
 	"time"
@@ -36,10 +37,11 @@
 type Corpus struct {
 	MutationLogger MutationLogger
 
-	mu           sync.RWMutex
-	githubIssues map[githubRepo]map[int32]*githubIssue // repo -> num -> issue
-	githubUsers  map[int64]*githubUser
-	githubRepos  []repoObj
+	mu               sync.RWMutex
+	githubIssues     map[githubRepo]map[int32]*githubIssue // repo -> num -> issue
+	githubUsers      map[int64]*githubUser
+	pollGithubIssues []polledGithubIssues
+	pollGitDirs      []polledGitCommits
 	// If true, log new commits
 	shouldLog bool
 	debug     bool
@@ -47,16 +49,20 @@
 	// TODO
 }
 
-type repoObj struct {
+type polledGithubIssues struct {
 	name      githubRepo
 	tokenFile string
 }
 
+type polledGitCommits struct {
+	repo *maintpb.GitRepo
+	dir  string
+}
+
 func NewCorpus(logger MutationLogger) *Corpus {
 	return &Corpus{
 		githubIssues:   make(map[githubRepo]map[int32]*githubIssue),
 		githubUsers:    make(map[int64]*githubUser),
-		githubRepos:    []repoObj{},
 		MutationLogger: logger,
 	}
 }
@@ -81,12 +87,34 @@
 func (c *Corpus) AddGithub(owner, repo, tokenFile string) {
 	c.mu.Lock()
 	defer c.mu.Unlock()
-	c.githubRepos = append(c.githubRepos, repoObj{
+	c.pollGithubIssues = append(c.pollGithubIssues, polledGithubIssues{
 		name:      githubRepo(owner + "/" + repo),
 		tokenFile: tokenFile,
 	})
 }
 
+// gerritProjNameRx is the pattern describing a Gerrit project name.
+// TODO: figure out if this is accurate.
+var gerritProjNameRx = regexp.MustCompile(`^[a-z0-9]+[a-z0-9\-\_]*$`)
+
+// AddGoGitRepo registers a git directory to have its metadata slurped into the corpus.
+// The goRepo is a name like "go" or "net". The dir is a path on disk.
+//
+// TODO(bradfitz): this whole interface is temporary. Make this
+// support any git repo and make this (optionally?) use the gitmirror
+// service later instead of a separate copy on disk.
+func (c *Corpus) AddGoGitRepo(goRepo, dir string) {
+	if !gerritProjNameRx.MatchString(goRepo) {
+		panic(fmt.Sprintf("bogus goRepo value %q", goRepo))
+	}
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	c.pollGitDirs = append(c.pollGitDirs, polledGitCommits{
+		repo: &maintpb.GitRepo{GoRepo: goRepo},
+		dir:  dir,
+	})
+}
+
 // githubRepo is a github org & repo, lowercase, joined by a '/',
 // such as "golang/go".
 type githubRepo string
@@ -509,15 +537,28 @@
 // Poll checks for new changes on all repositories being tracked by the Corpus.
 func (c *Corpus) Poll(ctx context.Context) error {
 	group, ctx := errgroup.WithContext(ctx)
-	for _, rp := range c.githubRepos {
+	for _, rp := range c.pollGithubIssues {
 		rp := rp
 		group.Go(func() error {
 			return c.PollGithubLoop(ctx, rp.name, rp.tokenFile)
 		})
 	}
+	for _, rp := range c.pollGitDirs {
+		rp := rp
+		group.Go(func() error {
+			return c.PollGitCommits(ctx, rp)
+		})
+	}
 	return group.Wait()
 }
 
+// PollGithubCommits polls for git commits in a directory.
+func (c *Corpus) PollGitCommits(ctx context.Context, conf polledGitCommits) error {
+	log.Printf("TODO: poll %v from %v", conf.repo, conf.dir)
+	select {} // TODO(bradfitz): actuall poll
+	return nil
+}
+
 // PollGithubLoop checks for new changes on a single Github repository and
 // updates the Corpus with any changes.
 func (c *Corpus) PollGithubLoop(ctx context.Context, rp githubRepo, tokenFile string) error {