go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/protoutil/tag.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package protoutil 16 17 import ( 18 "fmt" 19 "sort" 20 "strconv" 21 "strings" 22 23 "google.golang.org/protobuf/proto" 24 25 "go.chromium.org/luci/common/data/strpair" 26 27 pb "go.chromium.org/luci/buildbucket/proto" 28 ) 29 30 // StringPairs converts a strpair.Map to a slice of StringPair messages. 31 func StringPairs(m strpair.Map) []*pb.StringPair { 32 ret := make([]*pb.StringPair, 0, len(m)) 33 for k, vs := range m { 34 for _, v := range vs { 35 ret = append(ret, &pb.StringPair{Key: k, Value: v}) 36 } 37 } 38 SortStringPairs(ret) 39 return ret 40 } 41 42 // SortStringPairs sorts string pairs. 43 func SortStringPairs(pairs []*pb.StringPair) { 44 sort.Slice(pairs, func(i, j int) bool { 45 switch { 46 case pairs[i].Key < pairs[j].Key: 47 return true 48 case pairs[i].Key > pairs[j].Key: 49 return false 50 default: 51 return pairs[i].Value < pairs[j].Value 52 } 53 }) 54 } 55 56 // StringPairMap converts a slice of StringPair messages to a strpair.Map. 57 func StringPairMap(pairs []*pb.StringPair) strpair.Map { 58 m := make(strpair.Map) 59 for _, pair := range pairs { 60 m.Add(pair.Key, pair.Value) 61 } 62 return m 63 } 64 65 // BuildSets returns all of the buildsets of the build. 66 func BuildSets(b *pb.Build) []string { 67 var result []string 68 for _, tag := range b.Tags { 69 if tag.Key == "buildset" { 70 result = append(result, tag.Value) 71 } 72 } 73 return result 74 } 75 76 // GerritBuildSet returns a buildset representation of c, 77 // e.g. "patch/gerrit/chromium-review.googlesource.com/677784/5" 78 func GerritBuildSet(c *pb.GerritChange) string { 79 return fmt.Sprintf("patch/gerrit/%s/%d/%d", c.Host, c.Change, c.Patchset) 80 } 81 82 // GerritChangeURL returns URL of the change. 83 func GerritChangeURL(c *pb.GerritChange) string { 84 return fmt.Sprintf("https://%s/c/%d/%d", c.Host, c.Change, c.Patchset) 85 } 86 87 // GitilesBuildSet returns a buildset representation of c. 88 // e.g. "commit/gitiles/chromium.googlesource.com/infra/luci/luci-go/+/b7a757f457487cd5cfe2dae83f65c5bc10e288b7" 89 // 90 // Returns an empty string if not all of {Host, Project, Id} are set. 91 func GitilesBuildSet(c *pb.GitilesCommit) string { 92 if c.Host == "" || c.Project == "" || c.Id == "" { 93 return "" 94 } 95 return fmt.Sprintf("commit/gitiles/%s/%s/+/%s", c.Host, c.Project, c.Id) 96 } 97 98 // GitilesRepoURL returns the URL for the gitiles repo. 99 // e.g. "https://chromium.googlesource.com/chromium/src" 100 func GitilesRepoURL(c *pb.GitilesCommit) string { 101 return fmt.Sprintf("https://%s/%s", c.Host, c.Project) 102 } 103 104 // GitilesCommitURL returns the URL for the gitiles commit. 105 // e.g. "https://chromium.googlesource.com/chromium/src/+/b7a757f457487cd5cfe2dae83f65c5bc10e288b7" 106 // or "https://chromium.googlesource.com/chromium/src/+/refs/heads/master" 107 // if id is not available. 108 func GitilesCommitURL(c *pb.GitilesCommit) string { 109 suffix := c.Id 110 if suffix == "" { 111 suffix = c.Ref 112 } 113 return fmt.Sprintf("%s/+/%s", GitilesRepoURL(c), suffix) 114 } 115 116 // ParseBuildSet tries to parse buildset as one of the known formats. 117 // May return *pb.GerritChange, *pb.GitilesCommit 118 // or nil. 119 func ParseBuildSet(buildSet string) proto.Message { 120 // fmt.Sscanf cannot be used for this parsing because 121 // var a, b string 122 // fmt.Scanf("a/b", "%s/%s", &a, &b) 123 // a == "a/b", b == "" 124 125 p := strings.Split(buildSet, "/") 126 for _, c := range p { 127 if c == "" { 128 return nil 129 } 130 } 131 n := len(p) 132 switch { 133 case n == 5 && p[0] == "patch" && p[1] == "gerrit": 134 gerrit := &pb.GerritChange{ 135 Host: p[2], 136 } 137 var err error 138 if gerrit.Change, err = strconv.ParseInt(p[3], 10, 64); err != nil { 139 return nil 140 } 141 if gerrit.Patchset, err = strconv.ParseInt(p[4], 10, 64); err != nil { 142 return nil 143 } 144 return gerrit 145 146 case n >= 5 && p[0] == "commit" && p[1] == "gitiles": 147 if p[n-2] != "+" || !looksLikeSha1(p[n-1]) { 148 return nil 149 } 150 return &pb.GitilesCommit{ 151 Host: p[2], 152 Project: strings.Join(p[3:n-2], "/"), // exclude plus 153 Id: p[n-1], 154 } 155 156 default: 157 return nil 158 } 159 } 160 161 func looksLikeSha1(s string) bool { 162 if len(s) != 40 { 163 return false 164 } 165 for _, c := range s { 166 switch { 167 case '0' <= c && c <= '9': 168 case 'a' <= c && c <= 'f': 169 default: 170 return false 171 } 172 } 173 return true 174 }