blob: b0bab273800cc783f4e7288a2a460aaf6f6b14d9 [file] [log] [blame]
package cq
import (
"bytes"
"fmt"
"regexp"
"strings"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/duration"
"go.chromium.org/luci/cq/api/config/v2"
)
const (
cqConfigHeader = `# See http://luci-config.appspot.com/schemas/projects:commit-queue.cfg for the
# documentation of this file format.
`
)
// WithUpdateCQConfig parses the given bytes as a Config, runs the given
// function to modify the Config, then returns the updated bytes.
func WithUpdateCQConfig(oldCfgBytes []byte, fn func(*config.Config) error) ([]byte, error) {
// Parse the Config.
var cfg config.Config
if err := proto.UnmarshalText(string(oldCfgBytes), &cfg); err != nil {
return nil, fmt.Errorf("Failed to parse config proto: %s", err)
}
// Run the passed-in func.
if err := fn(&cfg); err != nil {
return nil, fmt.Errorf("Config modification failed: %s", err)
}
// Write the new config bytes.
var buf bytes.Buffer
if err := proto.MarshalText(&buf, &cfg); err != nil {
return nil, fmt.Errorf("Failed to encode config file: %s", err)
}
// We like curly braces instead of angle brackets.
newCfgStr := buf.String()
newCfgStr = strings.Replace(newCfgStr, ": <", " {", -1)
newCfgStr = strings.Replace(newCfgStr, ">", "}", -1)
return []byte(cqConfigHeader + newCfgStr), nil
}
// CloneBranch updates the given CQ config to create a config for a new
// branch based on a given existing branch. Optionally, include experimental
// tryjobs, include the tree-is-open check, and exclude trybots matching regular
// expressions.
func CloneBranch(cfg *config.Config, oldBranch, newBranch string, includeExperimental, includeTreeCheck bool, excludeTrybotRegexp []*regexp.Regexp) error {
// Find the CQ config for the old branch.
oldRef := fmt.Sprintf("refs/heads/%s", oldBranch)
oldCg, oldGerrit, oldProject, err := MatchConfigGroup(cfg, oldRef)
if err != nil {
return fmt.Errorf("Failed to find config group for %q: %s", oldRef, err)
}
if oldCg == nil {
return fmt.Errorf("No config group matches %q", oldRef)
}
// Create the CQ config for the new branch.
newCg := &config.ConfigGroup{
Name: newBranch,
Gerrit: []*config.ConfigGroup_Gerrit{
{
Url: oldGerrit.Url,
Projects: []*config.ConfigGroup_Gerrit_Project{
{
Name: oldProject.Name,
RefRegexp: []string{
fmt.Sprintf("refs/heads/%s", newBranch),
},
},
},
},
},
}
if oldCg.CombineCls != nil {
newCg.CombineCls = &config.CombineCLs{}
if oldCg.CombineCls.StabilizationDelay != nil {
newCg.CombineCls.StabilizationDelay = &duration.Duration{
Seconds: oldCg.CombineCls.StabilizationDelay.Seconds,
Nanos: oldCg.CombineCls.StabilizationDelay.Nanos,
}
}
}
if oldCg.Verifiers != nil {
newCg.Verifiers = &config.Verifiers{
GerritCqAbility: oldCg.Verifiers.GerritCqAbility,
Fake: oldCg.Verifiers.Fake,
}
if includeTreeCheck {
newCg.Verifiers.TreeStatus = oldCg.Verifiers.TreeStatus
}
if oldCg.Verifiers.Tryjob != nil {
tryjobs := make([]*config.Verifiers_Tryjob_Builder, 0, len(oldCg.Verifiers.Tryjob.Builders))
for _, tj := range oldCg.Verifiers.Tryjob.Builders {
exclude := false
for _, re := range excludeTrybotRegexp {
if re.MatchString(tj.Name) {
exclude = true
break
}
}
if tj.ExperimentPercentage != 0.0 && !includeExperimental {
exclude = true
}
if !exclude {
tryjobs = append(tryjobs, tj)
}
}
newCg.Verifiers.Tryjob = &config.Verifiers_Tryjob{
Builders: tryjobs,
RetryConfig: oldCg.Verifiers.Tryjob.RetryConfig,
}
}
}
cfg.ConfigGroups = append(cfg.ConfigGroups, newCg)
return nil
}
// DeleteBranch updates the given CQ config to delete the config matching the
// given branch.
func DeleteBranch(cfg *config.Config, branch string) error {
cg, _, _, err := MatchConfigGroup(cfg, fmt.Sprintf("refs/heads/%s", branch))
if err != nil {
return err
}
newGroups := make([]*config.ConfigGroup, 0, len(cfg.ConfigGroups))
for _, g := range cfg.ConfigGroups {
if g != cg {
newGroups = append(newGroups, g)
}
}
cfg.ConfigGroups = newGroups
return nil
}