cmd/checker: use query by scheme

Previously, we would get a list of checkers on startup, so newly
added checkers would never be detected by Gerrit. The per-scheme
query lets the linter pick up new checkers on the fly.

Change-Id: I2a0b5127494726c9fdb499ab912e475721cbf5bd
diff --git a/cmd/checker/checker.go b/cmd/checker/checker.go
index 4cfc13f..f57ebc7 100644
--- a/cmd/checker/checker.go
+++ b/cmd/checker/checker.go
@@ -34,13 +34,10 @@
 type gerritChecker struct {
 	server *gerrit.Server
 
-	// UUID => language
-	checkerUUIDs []string
-
 	todo chan *gerrit.PendingChecksInfo
 }
 
-const checkerScheme = "fmt:"
+const checkerScheme = "fmt"
 
 // ListCheckers returns all the checkers that conform to our scheme.
 func (gc *gerritChecker) ListCheckers() ([]*gerrit.CheckerInfo, error) {
@@ -56,7 +53,7 @@
 
 	filtered := out[:0]
 	for _, o := range out {
-		if !strings.HasPrefix(o.UUID, checkerScheme) {
+		if !strings.HasPrefix(o.UUID, checkerScheme+":") {
 			continue
 		}
 		if _, ok := checkerLanguage(o.UUID); !ok {
@@ -73,7 +70,7 @@
 	hash := sha1.New()
 	hash.Write([]byte(repo))
 
-	uuid := fmt.Sprintf("%s%s-%x", checkerScheme, language, hash.Sum(nil))
+	uuid := fmt.Sprintf("%s:%s-%x", checkerScheme, language, hash.Sum(nil))
 	in := gerrit.CheckerInput{
 		UUID:        uuid,
 		Name:        language + " formatting",
@@ -106,7 +103,7 @@
 }
 
 func checkerLanguage(uuid string) (string, bool) {
-	uuid = strings.TrimPrefix(uuid, checkerScheme)
+	uuid = strings.TrimPrefix(uuid, checkerScheme+":")
 	fields := strings.Split(uuid, "-")
 	if len(fields) != 2 {
 		return "", false
@@ -120,14 +117,6 @@
 		todo:   make(chan *gerrit.PendingChecksInfo, 5),
 	}
 
-	if out, err := gc.ListCheckers(); err != nil {
-		return nil, err
-	} else {
-		for _, checker := range out {
-			gc.checkerUUIDs = append(gc.checkerUUIDs, checker.UUID)
-		}
-	}
-
 	go gc.pendingLoop()
 	return gc, nil
 }
@@ -192,22 +181,20 @@
 
 func (c *gerritChecker) pendingLoop() {
 	for {
-		for _, uuid := range c.checkerUUIDs {
-			pending, err := c.server.PendingChecks(uuid)
-			if err != nil {
-				log.Printf("PendingChecks: %v", err)
-				continue
-			}
-			if len(pending) == 0 {
-				log.Printf("no pending checks")
-			}
+		pending, err := c.server.PendingChecksByScheme(checkerScheme)
+		if err != nil {
+			log.Printf("PendingChecksByScheme: %v", err)
+			continue
+		}
+		if len(pending) == 0 {
+			log.Printf("no pending checks")
+		}
 
-			for _, pc := range pending {
-				select {
-				case c.todo <- pc:
-				default:
-					log.Println("too busy; dropping pending check.")
-				}
+		for _, pc := range pending {
+			select {
+			case c.todo <- pc:
+			default:
+				log.Println("too busy; dropping pending check.")
 			}
 		}
 		// TODO: real rate limiting.
@@ -274,7 +261,7 @@
 				status = statusIrrelevant
 			} else if err != nil {
 				status = statusFail
-				log.Printf("checkChange(%s, %d, %q): %v", changeID, psID, lang)
+				log.Printf("checkChange(%s, %d, %q): %v", changeID, psID, lang, err)
 				msgs = []string{fmt.Sprintf("tool failure: %v", err)}
 			} else if len(msgs) == 0 {
 				status = statusSuccessful
diff --git a/gerrit/server.go b/gerrit/server.go
index e23200b..5072eb1 100644
--- a/gerrit/server.go
+++ b/gerrit/server.go
@@ -165,6 +165,27 @@
 	return &Change{files}, nil
 }
 
+func (s *Server) PendingChecksByScheme(scheme string) ([]*PendingChecksInfo, error) {
+	u := s.URL
+
+	// The trailing '/' handling is really annoying.
+	u.Path = path.Join(u.Path, "a/plugins/checks/checks.pending/") + "/"
+
+	q := "scheme:" + scheme
+	u.RawQuery = "query=" + q
+	content, err := s.Get(&u)
+	if err != nil {
+		return nil, err
+	}
+
+	var out []*PendingChecksInfo
+	if err := Unmarshal(content, &out); err != nil {
+		return nil, err
+	}
+
+	return out, nil
+}
+
 func (s *Server) PendingChecks(checkerUUID string) ([]*PendingChecksInfo, error) {
 	u := s.URL