Skip to content
quarry Artifact [3e4a8e07cb]
Login

Artifact [3e4a8e07cb]

Artifact 3e4a8e07cb295b593c1cf1ba19f42994c12aa539dd2b91dbda146a3eb7607ca0:


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 '', -- '' tip | premerge (RFC 0009)
	PRIMARY KEY (repo, branch)
);`

// CheckRule is one row: which branches, which checks, and how the
// branch is tested — "" for the branch tip, "premerge" for the merged
// tree (RFC 0009 §premerge).
type CheckRule struct {
	Branch string
	Checks []string
	Mode   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, mode 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, &c.Mode); 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.
// mode is "" (test the tip) or "premerge" (test the merged tree).
func (d *DB) SetCheckRule(repo, branch string, checks []string, mode string) error {
	branch = strings.TrimSpace(branch)
	if branch == "" {
		return nil
	}
	if mode != "" && mode != "premerge" {
		mode = ""
	}
	names := normalizeOwners(checks)
	if len(names) == 0 {
		_, err := d.db.Exec(`DELETE FROM branch_required_checks WHERE repo = ? AND branch = ?`,
			repo, branch)
		return err
	}
	_, err := d.db.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, " "), mode)
	return err
}

// PremergeMode says whether a target branch is tested as the merged
// tree: any matching rule asking for premerge asks for it.
func (d *DB) PremergeMode(repo, branch string) bool {
	rules, err := d.CheckRules(repo)
	if err != nil {
		return false
	}
	for _, rule := range rules {
		if rule.Mode == "premerge" && branchMatches(rule.Branch, branch) {
			return true
		}
	}
	return false
}

// 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
}