cmd/gerritbot: abandon CLs imported from PRs that have been closed

When the code checks if the PR has been closed, we should also abandon
its corresponding CL, so add this behavior.

Also, while I'm here, I fixed a few staticcheck issues that gopls
noticed :)

Fixes golang/go#23850

Change-Id: Ifd7700612802edb68a232f08436109e71e31fb41
Reviewed-on: https://go-review.googlesource.com/c/build/+/234179
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/gerritbot/gerritbot.go b/cmd/gerritbot/gerritbot.go
index caaa110..e50a834 100644
--- a/cmd/gerritbot/gerritbot.go
+++ b/cmd/gerritbot/gerritbot.go
@@ -334,16 +334,23 @@
 			return nil
 		}
 		return ghr.ForeachIssue(func(issue *maintner.GitHubIssue) error {
+			ctx := context.Background()
 			if issue.PullRequest && issue.Closed {
 				// Clean up any reference of closed CLs within pendingCLs.
 				shortLink := githubShortLink(id.Owner, id.Repo, int(issue.Number))
 				delete(b.pendingCLs, shortLink)
+				if cl, ok := b.importedPRs[shortLink]; ok {
+					// The CL associated with the PR is still open since it's
+					// present in importedPRs, so abandon it.
+					if err := b.abandonCL(ctx, cl, shortLink); err != nil {
+						log.Printf("abandonCL(ctx, https://golang.org/cl/%v, %q): %v", cl.Number, shortLink, err)
+					}
+				}
 				return nil
 			}
 			if issue.Closed || !issue.PullRequest || !issue.HasLabel("cla: yes") {
 				return nil
 			}
-			ctx := context.Background()
 			pr, err := b.getFullPR(ctx, id.Owner, id.Repo, int(issue.Number))
 			if err != nil {
 				log.Printf("getFullPR(ctx, %q, %q, %d): %v", id.Owner, id.Repo, issue.Number, err)
@@ -437,7 +444,7 @@
 // gerritMessageAuthorID returns the Gerrit Account ID of the author of m.
 func gerritMessageAuthorID(m *maintner.GerritMessage) (int, error) {
 	email := m.Author.Email()
-	if strings.Index(email, "@") == -1 {
+	if !strings.Contains(email, "@") {
 		return -1, fmt.Errorf("message author email %q does not contain '@' character", email)
 	}
 	i, err := strconv.Atoi(strings.Split(email, "@")[0])
@@ -565,6 +572,18 @@
 	return nil
 }
 
+func (b *bot) abandonCL(ctx context.Context, cl *maintner.GerritCL, shortLink string) error {
+	if *dryRun {
+		log.Printf("[dry run] would abandon https://golang.org/cl/%v", cl.Number)
+		return nil
+	}
+	// Due to issues like https://golang.org/issue/28226, Gerrit may take time
+	// to catch up on the fact that a CL has been abandoned. We may have to
+	// make sure that we do not attempt to abandon the same CL multiple times.
+	msg := fmt.Sprintf("GitHub PR %s has been closed.", shortLink)
+	return b.gerritClient.AbandonChange(ctx, cl.ChangeID(), msg)
+}
+
 // getAbandonReason returns the last abandon reason in ch,
 // or the empty string if a reason doesn't exist.
 func getAbandonReason(ch *gerrit.ChangeInfo) string {
@@ -830,7 +849,7 @@
 	if resp == nil {
 		return
 	}
-	log.Printf("GitHub: %d/%d calls remaining; Reset in %v", resp.Rate.Remaining, resp.Rate.Limit, resp.Rate.Reset.Sub(time.Now()))
+	log.Printf("GitHub: %d/%d calls remaining; Reset in %v", resp.Rate.Remaining, resp.Rate.Limit, time.Until(resp.Rate.Reset.Time))
 }
 
 // postGitHubMessageNoDup ensures that the message being posted on an issue does not already have the