blob: 973a6290fdc427214e4cff49fd6408d9272db7c6 [file] [log] [blame]
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package maintner mirrors, searches, syncs, and serves Git, Github,
// and Gerrit metadata.
//
// Maintner is short for "Maintainer". This package is intended for
// use by many tools. The name of the daemon that serves the maintner
// data to other tools is "maintnerd".
package maintner
import (
"context"
"sync"
"time"
"golang.org/x/build/maintner/maintpb"
)
// Corpus holds all of a project's metadata.
type Corpus struct {
// ... TODO
mu sync.RWMutex
githubIssues map[githubRepo]map[int32]*githubIssue // repo -> num -> issue
githubUsers map[int64]*githubUser
}
// githubRepo is a github org & repo, lowercase, joined by a '/',
// such as "golang/go".
type githubRepo string
// githubUser represents a github user.
// It is a subset of https://developer.github.com/v3/users/#get-a-single-user
type githubUser struct {
ID int64
Login string
}
// githubIssue represents a github issue.
// See https://developer.github.com/v3/issues/#get-a-single-issue
type githubIssue struct {
ID int64
Number int32
Closed bool
User *githubUser
Created time.Time
Updated time.Time
Body string
// TODO Comments ...
}
// A MutationSource yields a log of mutations that will catch a corpus
// back up to the present.
type MutationSource interface {
// GetMutations returns a channel of mutations.
// The channel should be closed at the end.
// All sends on the returned channel should select
// on the provided context.
GetMutations(context.Context) <-chan *maintpb.Mutation
}
func (c *Corpus) processMutations(ctx context.Context, src MutationSource) error {
ch := src.GetMutations(ctx)
done := ctx.Done()
c.mu.Lock()
defer c.mu.Unlock()
for {
select {
case <-done:
return ctx.Err()
case m, ok := <-ch:
if !ok {
return nil
}
c.processMutationLocked(m)
}
}
}
// c.mu must be held.
func (c *Corpus) processMutationLocked(m *maintpb.Mutation) {
if im := m.GithubIssue; im != nil {
c.processGithubIssueMutation(im)
}
// TODO: more...
}
func (c *Corpus) repoKey(owner, repo string) githubRepo {
if owner == "" || repo == "" {
return ""
}
// TODO: avoid garbage, use interned strings? profile later
// once we have gigabytes of mutation logs to slurp at
// start-up. (The same thing mattered for Camlistore start-up
// time at least)
return githubRepo(owner + "/" + repo)
}
func (c *Corpus) getGithubUser(pu *maintpb.GithubUser) *githubUser {
if pu == nil {
return nil
}
if u := c.githubUsers[pu.Id]; u != nil {
if pu.Login != "" && pu.Login != u.Login {
u.Login = pu.Login
}
return u
}
if c.githubUsers == nil {
c.githubUsers = make(map[int64]*githubUser)
}
u := &githubUser{
ID: pu.Id,
Login: pu.Login,
}
c.githubUsers[pu.Id] = u
return u
}
func (c *Corpus) processGithubIssueMutation(m *maintpb.GithubIssueMutation) {
k := c.repoKey(m.Owner, m.Repo)
if k == "" {
// TODO: errors? return false? skip for now.
return
}
if m.Number == 0 {
return
}
issueMap, ok := c.githubIssues[k]
if !ok {
if c.githubIssues == nil {
c.githubIssues = make(map[githubRepo]map[int32]*githubIssue)
}
issueMap = make(map[int32]*githubIssue)
c.githubIssues[k] = issueMap
}
gi, ok := issueMap[m.Number]
if !ok {
gi = &githubIssue{
Number: m.Number,
User: c.getGithubUser(m.User),
}
issueMap[m.Number] = gi
}
if m.Body != "" {
gi.Body = m.Body
}
// TODO: times, etc.
}
// PopulateFromServer populates the corpus from a maintnerd server.
func (c *Corpus) PopulateFromServer(ctx context.Context, serverURL string) error {
panic("TODO")
}
// PopulateFromDisk populates the corpus from a set of mutation logs
// in a local directory.
func (c *Corpus) PopulateFromDisk(ctx context.Context, dir string) error {
panic("TODO")
}
// PopulateFromAPIs populates the corpus using API calls to
// the upstream Git, Github, and/or Gerrit servers.
func (c *Corpus) PopulateFromAPIs(ctx context.Context) error {
panic("TODO")
}