blob: 23d94d2efac36eb7c2547b8d4c9b1fc4c2c2ec4c [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package protoutil
import (
"fmt"
"sort"
"strconv"
"strings"
"google.golang.org/protobuf/proto"
"go.chromium.org/luci/common/data/strpair"
pb "go.chromium.org/luci/buildbucket/proto"
)
// StringPairs converts a strpair.Map to a slice of StringPair messages.
func StringPairs(m strpair.Map) []*pb.StringPair {
ret := make([]*pb.StringPair, 0, len(m))
for k, vs := range m {
for _, v := range vs {
ret = append(ret, &pb.StringPair{Key: k, Value: v})
}
}
SortStringPairs(ret)
return ret
}
// SortStringPairs sorts string pairs.
func SortStringPairs(pairs []*pb.StringPair) {
sort.Slice(pairs, func(i, j int) bool {
switch {
case pairs[i].Key < pairs[j].Key:
return true
case pairs[i].Key > pairs[j].Key:
return false
default:
return pairs[i].Value < pairs[j].Value
}
})
}
// StringPairMap converts a slice of StringPair messages to a strpair.Map.
func StringPairMap(pairs []*pb.StringPair) strpair.Map {
m := make(strpair.Map)
for _, pair := range pairs {
m.Add(pair.Key, pair.Value)
}
return m
}
// BuildSets returns all of the buildsets of the build.
func BuildSets(b *pb.Build) []string {
var result []string
for _, tag := range b.Tags {
if tag.Key == "buildset" {
result = append(result, tag.Value)
}
}
return result
}
// GerritBuildSet returns a buildset representation of c,
// e.g. "patch/gerrit/chromium-review.googlesource.com/677784/5"
func GerritBuildSet(c *pb.GerritChange) string {
return fmt.Sprintf("patch/gerrit/%s/%d/%d", c.Host, c.Change, c.Patchset)
}
// GerritChangeURL returns URL of the change.
func GerritChangeURL(c *pb.GerritChange) string {
return fmt.Sprintf("https://%s/c/%d/%d", c.Host, c.Change, c.Patchset)
}
// GitilesBuildSet returns a buildset representation of c.
// e.g. "commit/gitiles/chromium.googlesource.com/infra/luci/luci-go/+/b7a757f457487cd5cfe2dae83f65c5bc10e288b7"
//
// Returns an empty string if not all of {Host, Project, Id} are set.
func GitilesBuildSet(c *pb.GitilesCommit) string {
if c.Host == "" || c.Project == "" || c.Id == "" {
return ""
}
return fmt.Sprintf("commit/gitiles/%s/%s/+/%s", c.Host, c.Project, c.Id)
}
// GitilesRepoURL returns the URL for the gitiles repo.
// e.g. "https://chromium.googlesource.com/chromium/src"
func GitilesRepoURL(c *pb.GitilesCommit) string {
return fmt.Sprintf("https://%s/%s", c.Host, c.Project)
}
// GitilesCommitURL returns the URL for the gitiles commit.
// e.g. "https://chromium.googlesource.com/chromium/src/+/b7a757f457487cd5cfe2dae83f65c5bc10e288b7"
// or "https://chromium.googlesource.com/chromium/src/+/refs/heads/master"
// if id is not available.
func GitilesCommitURL(c *pb.GitilesCommit) string {
suffix := c.Id
if suffix == "" {
suffix = c.Ref
}
return fmt.Sprintf("%s/+/%s", GitilesRepoURL(c), suffix)
}
// ParseBuildSet tries to parse buildset as one of the known formats.
// May return *pb.GerritChange, *pb.GitilesCommit
// or nil.
func ParseBuildSet(buildSet string) proto.Message {
// fmt.Sscanf cannot be used for this parsing because
// var a, b string
// fmt.Scanf("a/b", "%s/%s", &a, &b)
// a == "a/b", b == ""
p := strings.Split(buildSet, "/")
for _, c := range p {
if c == "" {
return nil
}
}
n := len(p)
switch {
case n == 5 && p[0] == "patch" && p[1] == "gerrit":
gerrit := &pb.GerritChange{
Host: p[2],
}
var err error
if gerrit.Change, err = strconv.ParseInt(p[3], 10, 64); err != nil {
return nil
}
if gerrit.Patchset, err = strconv.ParseInt(p[4], 10, 64); err != nil {
return nil
}
return gerrit
case n >= 5 && p[0] == "commit" && p[1] == "gitiles":
if p[n-2] != "+" || !looksLikeSha1(p[n-1]) {
return nil
}
return &pb.GitilesCommit{
Host: p[2],
Project: strings.Join(p[3:n-2], "/"), // exclude plus
Id: p[n-1],
}
default:
return nil
}
}
func looksLikeSha1(s string) bool {
if len(s) != 40 {
return false
}
for _, c := range s {
switch {
case '0' <= c && c <= '9':
case 'a' <= c && c <= 'f':
default:
return false
}
}
return true
}