dmitri.shuralyov.com/service/change/...

gerritapi: add support for labels via Gerrit CL hashtags

It's helpful to visualize hashtags applied to CLs.
Do so by mapping them to labels.
dmitshur committed 3 months ago commit a289f548ed13ec8989f684f2d1a0e2ad2db411d8
gerritapi/gerritapi.go
@@ -2,20 +2,22 @@
 package gerritapi
 
 import (
 	"context"
 	"fmt"
+	"log"
 	"net/http"
 	"os"
 	"sort"
 	"strconv"
 	"strings"
 	"unicode"
 
 	"dmitri.shuralyov.com/service/change"
 	"dmitri.shuralyov.com/state"
 	"github.com/andygrunwald/go-gerrit"
+	"github.com/shurcooL/issues"
 	"github.com/shurcooL/users"
 )
 
 // NewService creates a Gerrit-backed issues.Service using given Gerrit client.
 // client must be non-nil.
@@ -61,14 +63,22 @@ func (s service) List(ctx context.Context, repo string, opt change.ListOptions)
 	var is []change.Change
 	for _, chg := range *cs {
 		if chg.Status == "DRAFT" {
 			continue
 		}
+		var labels []issues.Label
+		for _, hashtag := range chg.Hashtags {
+			labels = append(labels, issues.Label{
+				Name:  hashtag,
+				Color: issues.RGB{R: 0xed, G: 0xed, B: 0xed}, // A default light gray.
+			})
+		}
 		is = append(is, change.Change{
 			ID:        uint64(chg.Number),
 			State:     changeState(chg.Status),
 			Title:     chg.Subject,
+			Labels:    labels,
 			Author:    s.gerritUser(chg.Owner),
 			CreatedAt: chg.Created.Time,
 			Replies:   len(chg.Messages),
 		})
 	}
@@ -96,14 +106,22 @@ func (s service) Get(ctx context.Context, repo string, id uint64) (change.Change
 		return change.Change{}, err
 	}
 	if chg.Status == "DRAFT" {
 		return change.Change{}, os.ErrNotExist
 	}
+	var labels []issues.Label
+	for _, hashtag := range chg.Hashtags {
+		labels = append(labels, issues.Label{
+			Name:  hashtag,
+			Color: issues.RGB{R: 0xed, G: 0xed, B: 0xed}, // A default light gray.
+		})
+	}
 	return change.Change{
 		ID:           id,
 		State:        changeState(chg.Status),
 		Title:        chg.Subject,
+		Labels:       labels,
 		Author:       s.gerritUser(chg.Owner),
 		CreatedAt:    chg.Created.Time,
 		Replies:      len(chg.Messages),
 		Commits:      len(chg.Revisions),
 		ChangedFiles: 0, // TODO.
@@ -283,10 +301,11 @@ func (s service) ListTimeline(ctx context.Context, repo string, id uint64, opt *
 		Body:      commitMessageBody(commit.Message),
 		Editable:  false,
 	})
 	for idx, message := range chg.Messages {
 		if strings.HasPrefix(message.Tag, "autogenerated:") {
+		Outer:
 			switch message.Tag[len("autogenerated:"):] {
 			case "gerrit:merged":
 				timeline = append(timeline, change.TimelineItem{
 					Actor:     s.gerritUser(message.Author),
 					CreatedAt: message.Date.Time,
@@ -346,10 +365,38 @@ func (s service) ListTimeline(ctx context.Context, repo string, id uint64, opt *
 				timeline = append(timeline, change.TimelineItem{
 					Actor:     s.gerritUser(message.Author),
 					CreatedAt: message.Date.Time,
 					Payload:   change.ReopenedEvent{},
 				})
+			case "gerrit:setHashtag":
+				var payload interface{} // One of change.LabeledEvent or change.UnlabeledEvent.
+				switch {
+				case strings.HasPrefix(message.Message, "Hashtag added: "):
+					hashtag := message.Message[len("Hashtag added: "):]
+					payload = change.LabeledEvent{
+						Label: issues.Label{
+							Name:  hashtag,
+							Color: issues.RGB{R: 0xed, G: 0xed, B: 0xed}, // A default light gray.
+						},
+					}
+				case strings.HasPrefix(message.Message, "Hashtag removed: "):
+					hashtag := message.Message[len("Hashtag removed: "):]
+					payload = change.UnlabeledEvent{
+						Label: issues.Label{
+							Name:  hashtag,
+							Color: issues.RGB{R: 0xed, G: 0xed, B: 0xed}, // A default light gray.
+						},
+					}
+				default:
+					log.Printf("unknown setHashtag message: %q", message.Message)
+					break Outer
+				}
+				timeline = append(timeline, change.TimelineItem{
+					Actor:     s.gerritUser(message.Author),
+					CreatedAt: message.Date.Time,
+					Payload:   payload,
+				})
 			}
 			continue
 		}
 		labels, body, ok := parseMessage(message.Message)
 		if !ok {