package oltp
import "strings"
// Required checks (RFC 0009 §verdicts): per-branch names that must be
// green before an accept proceeds. Stored beside RFC 0007's protection
// rules and matched the same way — a branch name or a glob — but in
// their own table, because a branch may require checks without naming
// owners, and blanking owners must keep meaning "rule removed".
const checksSchema = `
CREATE TABLE IF NOT EXISTS branch_required_checks (
repo TEXT NOT NULL,
branch TEXT NOT NULL, -- a name or a glob: trunk, release-*
checks TEXT NOT NULL, -- check names, space-separated
mode TEXT NOT NULL DEFAULT '', -- unused since quarry#290: a landing's checks always test its merge
PRIMARY KEY (repo, branch)
);`
// CheckRule is one row: which branches, and which checks a landing onto
// them needs green on its merge.
type CheckRule struct {
Branch string
Checks []string
}
// CheckRules lists a repository's rules, for the settings page.
func (d *DB) CheckRules(repo string) ([]CheckRule, error) {
rows, err := d.db.Query(`SELECT branch, checks FROM branch_required_checks
WHERE repo = ? ORDER BY branch`, repo)
if err != nil {
return nil, err
}
defer rows.Close()
var out []CheckRule
for rows.Next() {
var c CheckRule
var checks string
if err := rows.Scan(&c.Branch, &checks); err != nil {
return nil, err
}
c.Checks = strings.Fields(checks)
out = append(out, c)
}
return out, rows.Err()
}
// SetCheckRule writes one rule; no checks removes it, like protection.
// A landing's checks always test its merge with the target (quarry#290),
// so a rule has no mode; the column stays empty.
func (d *DB) SetCheckRule(repo, branch string, checks []string) error {
branch = strings.TrimSpace(branch)
if branch == "" {
return nil
}
names := normalizeOwners(checks)
if len(names) == 0 {
_, err := d.w.Exec(`DELETE FROM branch_required_checks WHERE repo = ? AND branch = ?`,
repo, branch)
return err
}
_, err := d.w.Exec(`INSERT INTO branch_required_checks (repo, branch, checks, mode)
VALUES (?, ?, ?, ?)
ON CONFLICT (repo, branch) DO UPDATE SET checks = excluded.checks, mode = excluded.mode`,
repo, branch, strings.Join(names, " "), "")
return err
}
// RequiredChecks answers what a target branch requires: every matching
// rule applies, exactly as protection reads (RFC 0007's matching).
func (d *DB) RequiredChecks(repo, branch string) []string {
rules, err := d.CheckRules(repo)
if err != nil {
return nil
}
seen := map[string]bool{}
var out []string
for _, rule := range rules {
if !BranchMatches(rule.Branch, branch) {
continue
}
for _, c := range rule.Checks {
if !seen[c] {
seen[c] = true
out = append(out, c)
}
}
}
return out
}