blob: 899c6062f11d11617962c4b910e4d6fd44497da8 [file] [log] [blame]
package repo_manager
import (
"context"
"errors"
"fmt"
"net/http"
"regexp"
"strconv"
"go.skia.org/infra/autoroll/go/codereview"
"go.skia.org/infra/autoroll/go/revision"
"go.skia.org/infra/go/gerrit"
)
type SemVerGCSRepoManagerConfig struct {
GCSRepoManagerConfig
// ShortRevRegex is a regular expression string which indicates
// what part of the revision ID string should be used as the shortened
// ID for display. If not specified, the full ID string is used.
ShortRevRegex string
// VersionRegex is a regular expression string containing one or more
// integer capture groups. The integers matched by the capture groups
// are compared, in order, when comparing two revisions.
VersionRegex string
}
func (c *SemVerGCSRepoManagerConfig) Validate() error {
if err := c.GCSRepoManagerConfig.Validate(); err != nil {
return err
}
if c.VersionRegex == "" {
return errors.New("VersionRegex is required.")
}
return nil
}
// parseSemanticVersion returns the set of integer versions which make up the
// given semantic version, as specified by the capture groups in the given
// Regexp.
func parseSemanticVersion(regex *regexp.Regexp, ver string) ([]int, error) {
matches := regex.FindStringSubmatch(ver)
if len(matches) > 1 {
matchInts := make([]int, len(matches)-1)
for idx, a := range matches[1:] {
i, err := strconv.Atoi(a)
if err != nil {
return matchInts, fmt.Errorf("Failed to parse int from regex match string; is the regex incorrect?")
}
matchInts[idx] = i
}
return matchInts, nil
} else {
return nil, errInvalidGCSVersion
}
}
// compareSemanticVersions returns 1 if A comes before B, -1 if A comes
// after B, and 0 if they are equal.
func compareSemanticVersions(a, b []int) int {
for i := 0; ; i++ {
if i == len(a) && i == len(b) {
return 0
} else if i == len(a) {
return 1
} else if i == len(b) {
return -1
}
if a[i] < b[i] {
return 1
} else if a[i] > b[i] {
return -1
}
}
}
// semVersion is an implementation of gcsVersion which uses semantic versioning.
type semVersion struct {
id string
version []int
}
// See documentation for gcsVersion interface.
func (v *semVersion) Compare(other gcsVersion) int {
a := v.version
b := other.(*semVersion).version
return compareSemanticVersions(a, b)
}
// See documentation for gcsVersion interface.
func (v *semVersion) Id() string {
return v.id
}
// See documentation for getGCSVersionFunc.
func getSemanticGCSVersion(regex *regexp.Regexp, rev *revision.Revision) (gcsVersion, error) {
ver, err := parseSemanticVersion(regex, rev.Id)
if err != nil {
return nil, err
}
return &semVersion{
id: rev.Id,
version: ver,
}, nil
}
// NewSemVerGCSRepoManager returns a gcsRepoManager which uses semantic
// versioning to compare object versions.
func NewSemVerGCSRepoManager(ctx context.Context, c *SemVerGCSRepoManagerConfig, workdir string, g gerrit.GerritInterface, serverURL string, client *http.Client, cr codereview.CodeReview, local bool) (RepoManager, error) {
if err := c.Validate(); err != nil {
return nil, err
}
versionRegex, err := regexp.Compile(c.VersionRegex)
if err != nil {
return nil, err
}
getGCSVersion := func(rev *revision.Revision) (gcsVersion, error) {
return getSemanticGCSVersion(versionRegex, rev)
}
var shortRevRegex *regexp.Regexp
if c.ShortRevRegex != "" {
shortRevRegex, err = regexp.Compile(c.ShortRevRegex)
if err != nil {
return nil, err
}
}
shortRev := func(id string) string {
if shortRevRegex != nil {
matches := shortRevRegex.FindStringSubmatch(id)
if len(matches) > 0 {
return matches[0]
}
// TODO(borenet): It'd be nice to log an error here to
// indicate that the regex might be incorrect, but this
// function may be called for revisions which are not
// valid and thus may not match the regex. That would
// cause an unhelpful error spew in the log.
}
return id
}
return newGCSRepoManager(ctx, &c.GCSRepoManagerConfig, workdir, g, serverURL, client, cr, local, getGCSVersion, shortRev)
}