blob: 9dd2f0187ac5f51018f784a0a3e46f6af4ed6c25 [file] [log] [blame]
// Copyright 2016 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 godash
import (
"bytes"
"encoding/gob"
"encoding/json"
"sort"
"strings"
"time"
)
// reviewScale is the relative weight of a single review compared to
// this many authored CLs.
const reviewScale = 1000000000
type reviewer struct {
addr string
count int64 // #reviews * reviewScale + #CLs
}
type reviewersByCount []reviewer
func (x reviewersByCount) Len() int { return len(x) }
func (x reviewersByCount) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x reviewersByCount) Less(i, j int) bool {
if x[i].count != x[j].count {
return x[i].count > x[j].count
}
return x[i].addr < x[j].addr
}
// Reviewers tracks the popularity of reviewers in a Git
// repository. It can be used to resolve e-mail addresses into short
// names and vice versa.
type Reviewers struct {
// data contains the information that should be serialized if
// the Reviewers struct is serialized. (MarshalJSON and
// UnmarshalJSON will transparently use data instead of the
// full Reviewers struct.)
data struct {
// IsReviewer maps full e-mail addresses to booleans.
IsReviewer map[string]bool // rsc@golang.org -> true
// CountByAddr maps full e-mail address to a score of
// the number of CLs authored and reviewed.
CountByAddr map[string]int64
// GitHubByAddr maps full e-mail address to GitHub username.
GitHubByAddr map[string]string
// LastSHA and LastTime track the SHA and time of the
// last commit included in these stats.
LastSHA string
LastTime time.Time
}
// addrByGitHub maps GitHub usernames to preferred address
// (the address with the highest review count).
addrByGitHub map[string]string
// mailLookup maps short names to full e-mail addresses.
mailLookup map[string]string // rsc -> rsc@golang.org
}
// IsReviewer reports whether the provided address is a known reviewer.
func (r *Reviewers) IsReviewer(addr string) bool {
return r.data.IsReviewer[addr]
}
// Shorten will potentially shorten a full e-mail address if the short
// version maps back to that full address.
func (r *Reviewers) Shorten(addr string) string {
if i := strings.Index(addr, "@"); i >= 0 {
if r.mailLookup[addr[:i]] == addr {
return addr[:i]
}
}
return addr
}
// Resolve takes a short username and returns the matching full e-mail
// address, or "" if the username could not be resolved.
func (r *Reviewers) Resolve(short string) string {
return r.mailLookup[short]
}
// Preferred takes an address and returns the preferred e-mail address
// for that user, which may be the same. It does this by resolving the
// GitHub username and then returning the address most-used for
// commits on that username.
func (r *Reviewers) Preferred(addr string) string {
if out := r.addrByGitHub[r.data.GitHubByAddr[addr]]; out != "" {
return out
}
return addr
}
// ResolveGitHub takes a GitHub login name and returns the matching
// full e-mail address, or "" if the name could not be resolved.
func (r *Reviewers) ResolveGitHub(login string) string {
return r.addrByGitHub[login]
}
// add increments a reviewer's count. recalculate must be called to
// regenerate the mail lookup table.
func (r *Reviewers) add(addr string, isReviewer bool) {
if !strings.Contains(addr, "@") {
return
}
if r.data.IsReviewer == nil {
r.data.IsReviewer = make(map[string]bool)
}
if r.data.CountByAddr == nil {
r.data.CountByAddr = make(map[string]int64)
}
if r.data.GitHubByAddr == nil {
r.data.GitHubByAddr = make(map[string]string)
}
if isReviewer {
r.data.IsReviewer[addr] = true
r.data.CountByAddr[addr] += reviewScale
} else {
r.data.CountByAddr[addr] += 1
}
}
func (r *Reviewers) recalculate() {
reviewers := []reviewer{}
for addr, count := range r.data.CountByAddr {
reviewers = append(reviewers, reviewer{addr, count})
}
sort.Sort(reviewersByCount(reviewers))
r.mailLookup = map[string]string{}
for _, rev := range reviewers {
short := rev.addr
if i := strings.Index(short, "@"); i >= 0 {
short = short[:i]
}
if r.mailLookup[short] == "" {
r.mailLookup[short] = rev.addr
}
}
r.addrByGitHub = map[string]string{}
for addr, user := range r.data.GitHubByAddr {
if r.addrByGitHub[user] == "" || r.data.CountByAddr[r.addrByGitHub[user]] < r.data.CountByAddr[addr] {
r.addrByGitHub[user] = addr
}
}
}
func (r *Reviewers) MarshalJSON() ([]byte, error) {
return json.Marshal(r.data)
}
func (r *Reviewers) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(b, &r.data); err != nil {
return err
}
r.recalculate()
return nil
}
func (r *Reviewers) GobEncode() ([]byte, error) {
var out bytes.Buffer
e := gob.NewEncoder(&out)
if err := e.Encode(&r.data); err != nil {
return nil, err
}
return out.Bytes(), nil
}
func (r *Reviewers) GobDecode(b []byte) error {
d := gob.NewDecoder(bytes.NewBuffer(b))
if err := d.Decode(&r.data); err != nil {
return err
}
r.recalculate()
return nil
}