cmd/gopherbot: add task to remove "wait-release" from CLs for tree opening

Only runs manually. We can run this whenever the tree reopens.

Change-Id: I003a8b69fd212ec3040a26855796ba997f1a5943
Reviewed-on: https://go-review.googlesource.com/129817
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
diff --git a/cmd/gopherbot/gopherbot.go b/cmd/gopherbot/gopherbot.go
index 5b1100a..a906037 100644
--- a/cmd/gopherbot/gopherbot.go
+++ b/cmd/gopherbot/gopherbot.go
@@ -230,6 +230,7 @@
 	fn   func(*gopherbot, context.Context) error
 }{
 	{"kicktrain", (*gopherbot).getOffKickTrain},
+	{"unwait-release", (*gopherbot).unwaitRelease},
 	{"freeze old issues", (*gopherbot).freezeOldIssues},
 	{"label proposals", (*gopherbot).labelProposals},
 	{"set subrepo milestones", (*gopherbot).setSubrepoMilestones},
@@ -615,6 +616,37 @@
 	return nil
 }
 
+// unwaitRelease changes any Gerrit CL with hashtag "wait-release"
+// into "ex-wait-release". This is run manually (with --only-run)
+// at the opening of a release cycle.
+func (b *gopherbot) unwaitRelease(ctx context.Context) error {
+	// We only run this task if it was explicitly requested via
+	// the --only-run flag.
+	if *onlyRun == "" {
+		return nil
+	}
+	cis, err := b.gerrit.QueryChanges(ctx, "hashtag:wait-release status:open")
+	if err != nil {
+		return nil
+	}
+	for _, ci := range cis {
+		if *dryRun {
+			log.Printf("[dry run] would remove hashtag 'wait-release' from CL %d", ci.ChangeNumber)
+			continue
+		}
+		_, err := b.gerrit.SetHashtags(ctx, ci.ID, gerrit.HashtagsInput{
+			Add:    []string{"ex-wait-release"},
+			Remove: []string{"wait-release"},
+		})
+		if err != nil {
+			log.Printf("https://golang.org/cl/%d: modifying hash tags: %v", ci.ChangeNumber, err)
+			return err
+		}
+		log.Printf("https://golang.org/cl/%d: removed wait-release", ci.ChangeNumber)
+	}
+	return nil
+}
+
 // freezeOldIssues locks any issue that's old and closed.
 // (Otherwise people find ancient bugs via searches and start asking questions
 // into a void and it's sad for everybody.)