blob: c031853dcbebaaa96cd0048d7efa503213e8a0ff [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"fmt"
"strconv"
"strings"
"github.com/andygrunwald/go-gerrit"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
bulkreview "chromium.googlesource.com/chromiumos/third_party/adhd.git/devtools/bulkreview"
"chromium.googlesource.com/chromiumos/third_party/adhd.git/devtools/bulkreview/labels"
)
const (
ignoreLabel = 1000
)
func main() {
cq := pflag.Int("cq", ignoreLabel, "set Commit-Queue vote")
cr := pflag.Int("cr", ignoreLabel, "set Code-Review vote")
v := pflag.Int("v", ignoreLabel, "set Verified vote")
limit := pflag.Int("limit", -1, "max number of CLs to vote")
reviewMessage := pflag.String("review-message", "", "code review message")
followCqDepend := pflag.BoolP("cq-depend", "d", false, "follow Cq-Depend recursively")
followRelationChain := pflag.BoolP("relation", "r", false, "follow relation chain")
checkKernelUpstream := pflag.Bool("check-kernel-upstream", false,
`Check for "No changes have been detected between this change and its upstream source!" by sean@poorly.run`,
)
removeAttention := pflag.Bool("remove-attention", false,
"remove the user from the attention set")
attentionUser := pflag.String("attention-user", bulkreview.UserSelf,
"the user to remove attention from, "+
"if --remove-attention is specified, "+
"defaults to self",
)
removeReviewer := pflag.String("remove-reviewer", "", "remove the user from the list of reviewers")
abandon := pflag.Bool("abandon", false, "abandon the specified CL")
abandonReason := pflag.String("abandon-reason", "", "The reason provided when --abandon is specified. Defaults to empty string.")
printTryjobCommand := pflag.Bool("print-tryjob-command", false, "Print cros tryjob command including the CLs")
print := pflag.Bool("print", false, "Print the CLs to stdout")
stalk := pflag.Bool("stalk", false, "perform actions on even ABANDONED or MERGED CLs")
pflag.Parse()
if *abandon && ((*cq != ignoreLabel) || (*cr != ignoreLabel) || (*v != ignoreLabel) || *removeAttention) {
logrus.Fatal("You won't want to vote on a CL that are going to abandoned.")
}
c, err := bulkreview.NewClient()
if err != nil {
logrus.Fatal(err)
}
var changes []bulkreview.Change
for _, arg := range pflag.Args() {
cl, err := bulkreview.ParseChange(arg)
if err != nil {
logrus.Fatal(err)
}
changes = append(changes, cl)
}
logrus.Printf("looking for dependencies of %s", changes)
changes, err = bulkreview.FindDependencies(
bulkreview.DependencyResolverOptions{
C: c,
FollowRelationChain: *followRelationChain,
FollowCqDepend: *followCqDepend,
Stalk: *stalk,
},
changes...,
)
if err != nil {
logrus.Fatal(err)
}
logrus.Println(len(changes), "changes in dependency chain")
if *checkKernelUpstream {
var checkedCLs []bulkreview.Change
allUpstream := true
logrus.Println("Checking kernel upstream...")
for i, cl := range changes {
ccl, ok := bulkreview.CheckKernelUpstream(c, cl)
checkedCLs = append(checkedCLs, ccl)
if ok {
logrus.Printf("%d/%d %s OK", i+1, len(changes), ccl)
} else {
logrus.Printf("%d/%d %s Missing", i+1, len(changes), ccl)
allUpstream = false
}
}
if !allUpstream {
logrus.Fatal("Not all CLs are upstream")
}
changes = checkedCLs
}
votes := map[string]string{}
if *cq != ignoreLabel {
votes[labels.CommitQueue] = strconv.Itoa(*cq)
}
if *cr != ignoreLabel {
votes[labels.CodeReview] = strconv.Itoa(*cr)
}
if *v != ignoreLabel {
votes[labels.Verified] = strconv.Itoa(*v)
}
if *limit != -1 && len(changes) > *limit {
changes = changes[:*limit]
}
logrus.Println(len(changes), "changes to consider")
for i, cl := range changes {
logrus.Printf("(%d/%d) %s", i+1, len(changes), cl)
cg := c.Host(cl.GerritHost)
if len(votes) > 0 || *reviewMessage != "" {
review := &gerrit.ReviewInput{
Labels: votes,
Message: *reviewMessage,
}
revision := bulkreview.RevisionCurrent
if cl.Revision != 0 {
revision = strconv.Itoa(cl.Revision)
}
r, _, err := cg.Changes.SetReview(cl.ID, revision, review)
if err != nil {
logrus.Fatal(err)
}
logrus.Printf("%+v", r)
}
if *removeAttention {
_, err := cg.Changes.RemoveAttention(
cl.ID,
*attentionUser,
&gerrit.AttentionSetInput{
Reason: fmt.Sprintf("bulkreview --remove-attention=%s", *attentionUser),
},
)
if err != nil {
logrus.Fatal(err)
}
logrus.Infof("Removed %s from attention set", *attentionUser)
}
if *removeReviewer != "" {
account, _, err := cg.Accounts.GetAccount(*removeReviewer)
if err != nil {
logrus.Error("cannot get account:", *removeReviewer)
}
reviewers, _, err := cg.Changes.ListReviewers(
cl.ID,
)
if err != nil {
logrus.Error(err)
}
for _, reviewer := range *reviewers {
if reviewer.AccountID == account.AccountID {
logrus.Infof("Removing reviewer %s", account.Email)
cg.Changes.DeleteReviewer(cl.ID, account.Email)
}
}
}
if *abandon {
msg := "bulkreview abandon"
if *abandonReason != "" {
msg = fmt.Sprintf("%s, reason: %s", msg, *abandonReason)
}
changeInfo, _, err := cg.Changes.AbandonChange(
cl.ID,
&gerrit.AbandonInput{
Message: msg,
},
)
if err != nil {
logrus.Fatal(err)
}
logrus.Infof("Abandon CL %s, current status: %s", cl.ID, changeInfo.Status)
}
}
if *printTryjobCommand {
command := &strings.Builder{}
command.WriteString("cros tryjob")
for _, cl := range changes {
fmt.Fprintf(command, " -g '%s'", cl.CrosTryjobID())
}
fmt.Println(command)
}
if *print {
for _, cl := range changes {
fmt.Println(cl)
}
}
}