blob: 4f03039cb9f7d7d012b070fea8f8400126922d45 [file] [log] [blame]
// Copyright 2024 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 reviews
import (
"iter"
"slices"
"strconv"
"time"
"golang.org/x/oscar/internal/gerrit"
)
// GerritReviewClient is a [gerrit.Client] with a mapping
// from account e-mail addresses to [Account] data.
// We do things this way because a lot of Gerrit change
// data is more or less the same for any Gerrit instance,
// but account information is not.
type GerritReviewClient struct {
GClient *gerrit.Client
Accounts AccountLookup
}
// GerritChange implements [Change] for a Gerrit CL.
type GerritChange struct {
Client *GerritReviewClient
Change *gerrit.Change
}
// ID returns the change ID.
func (gc *GerritChange) ID() string {
return strconv.Itoa(gc.Client.GClient.ChangeNumber(gc.Change))
}
// Status returns the change status.
func (gc *GerritChange) Status() Status {
switch gc.Client.GClient.ChangeStatus(gc.Change) {
case "MERGED":
return StatusSubmitted
case "ABANDONED":
return StatusClosed
default:
if gc.Client.GClient.ChangeWorkInProgress(gc.Change) {
return StatusDoNotReview
}
return StatusReady
}
}
// Author returns the change author.
func (gc *GerritChange) Author() Account {
gai := gc.Client.GClient.ChangeOwner(gc.Change)
return gc.Client.Accounts.Lookup(gai.Email)
}
// Created returns the time that the change was created.
func (gc *GerritChange) Created() time.Time {
ct := gc.Client.GClient.ChangeTimes(gc.Change)
return ct.Created
}
// Updated returns the time that the change was last updated.
func (gc *GerritChange) Updated() time.Time {
ct := gc.Client.GClient.ChangeTimes(gc.Change)
return ct.Updated
}
// UpdatedByAuthor returns the time that the change was updated by the
// original author.
func (gc *GerritChange) UpdatedByAuthor() time.Time {
author := gc.Client.GClient.ChangeOwner(gc.Change)
revs := gc.Client.GClient.ChangeRevisions(gc.Change)
for _, rev := range slices.Backward(revs) {
if rev.Uploader.Email == author.Email {
return rev.Created.Time()
}
}
return time.Time{}
}
// Subject returns the change subject.
func (gc *GerritChange) Subject() string {
return gc.Client.GClient.ChangeSubject(gc.Change)
}
// Description returns the full change description.
func (gc *GerritChange) Description() string {
return gc.Client.GClient.ChangeDescription(gc.Change)
}
// Reviewers returns the assigned reviewers.
func (gc *GerritChange) Reviewers() []Account {
reviewers := gc.Client.GClient.ChangeReviewers(gc.Change)
ret := make([]Account, 0, len(reviewers))
for _, rev := range reviewers {
ret = append(ret, gc.Client.Accounts.Lookup(rev.Email))
}
return ret
}
// Reviewed returns the accounts that have reviewed the change.
// We treat any account that has sent a message about the change
// as a reviewer.
func (gc *GerritChange) Reviewed() []Account {
reviewers := make(map[string]bool)
msgs := gc.Client.GClient.ChangeMessages(gc.Change)
for _, msg := range msgs {
if msg.RealAuthor != nil {
reviewers[msg.RealAuthor.Email] = true
} else if msg.Author != nil {
reviewers[msg.Author.Email] = true
}
}
num := gc.Client.GClient.ChangeNumber(gc.Change)
project := gc.Client.GClient.ChangeProject(gc.Change)
commentMap := gc.Client.GClient.Comments(project, num)
for _, comments := range commentMap {
for _, comment := range comments {
if comment.Author != nil {
reviewers[comment.Author.Email] = true
}
}
}
owner := gc.Client.GClient.ChangeOwner(gc.Change)
delete(reviewers, owner.Email)
ret := make([]Account, 0, len(reviewers))
for email := range reviewers {
ret = append(ret, gc.Client.Accounts.Lookup(email))
}
return ret
}
// Needs returns missing requirements for submittal.
// This implementation does what we can, but most projects will need
// their own version of this method.
func (gc *GerritChange) Needs() Needs {
hasReview, hasMaintainerReview := false, false
for _, review := range gc.Reviewed() {
switch review.Authority() {
case AuthorityReviewer:
hasReview = true
case AuthorityMaintainer, AuthorityOwner:
hasMaintainerReview = true
}
}
if hasMaintainerReview {
// We don't really know if the change can be submitted.
return 0
} else if hasReview {
return NeedsMaintainerReview
} else {
return NeedsReview
}
}
// GerritChanges converts from an iterator over [gerrit.Change]
// values into an iterator over [GerritChange] values.
func GerritChanges(cl *gerrit.Client, accounts AccountLookup, it iter.Seq[*gerrit.Change]) iter.Seq[*GerritChange] {
return func(yield func(*GerritChange) bool) {
grc := &GerritReviewClient{
GClient: cl,
Accounts: accounts,
}
for ch := range it {
gc := &GerritChange{
Client: grc,
Change: ch,
}
if !yield(gc) {
return
}
}
}
}