blob: 05dae3f682d0e0fdaf626d77ca6890a79a0db1ab [file] [log] [blame]
// Copyright 2021 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 changelist
import (
"fmt"
"strconv"
"strings"
"go.chromium.org/luci/common/errors"
)
// ExternalID is a unique CL ID deterministically constructed based on CL data.
//
// Currently, only Gerrit is supported.
type ExternalID string
// GobID makes an ExternalID for a Gerrit CL.
//
// Host is typically "something-review.googlesource.com".
// Change is a number, e.g. 2515619 for
// https://chromium-review.googlesource.com/c/infra/luci/luci-go/+/2515619
func GobID(host string, change int64) (ExternalID, error) {
if strings.ContainsRune(host, '/') {
return "", errors.Reason("invalid host %q: must not contain /", host).Err()
}
return ExternalID(fmt.Sprintf("gerrit/%s/%d", host, change)), nil
}
// MustGobID is like GobID but panics on error.
func MustGobID(host string, change int64) ExternalID {
ret, err := GobID(host, change)
if err != nil {
panic(err)
}
return ret
}
// ParseGobID returns Gerrit host and change if this is a GobID.
func (eid ExternalID) ParseGobID() (host string, change int64, err error) {
parts := strings.Split(string(eid), "/")
if len(parts) != 3 || parts[0] != "gerrit" {
err = errors.Reason("%q is not a valid GobID", eid).Err()
return
}
host = parts[1]
change, err = strconv.ParseInt(parts[2], 10, 63)
if err != nil {
err = errors.Annotate(err, "%q is not a valid GobID", eid).Err()
}
return
}
// URL returns URL of the CL.
func (eid ExternalID) URL() (string, error) {
parts := strings.Split(string(eid), "/")
if len(parts) < 2 {
return "", errors.Reason("invalid ExternalID: %q", eid).Err()
}
switch kind := parts[0]; kind {
case "gerrit":
return fmt.Sprintf("https://%s/c/%s", parts[1], parts[2]), nil
default:
return "", errors.Reason("unrecognized ExternalID: %q", eid).Err()
}
}
// MustURL is like `URL()` but panic on err.
func (eid ExternalID) MustURL() string {
ret, err := eid.URL()
if err != nil {
panic(err)
}
return ret
}
func (eid ExternalID) kind() (string, error) {
s := string(eid)
idx := strings.IndexRune(s, '/')
if idx <= 0 {
return "", errors.Reason("invalid ExternalID: %q", s).Err()
}
return s[:idx], nil
}
// JoinExternalURLs the URL of given ExternalIDs.
//
// Panics if any of the ExternalIDs is invalid.
func JoinExternalURLs(ids []ExternalID, sep string) string {
var s strings.Builder
for i, id := range ids {
fmt.Fprint(&s, id.MustURL())
if i != len(ids)-1 {
fmt.Fprint(&s, sep)
}
}
return s.String()
}