Skip to content
quarry Artifact [82d5196dd3]
Login

Artifact [82d5196dd3]

Artifact 82d5196dd34e79f67a88cd1a7314db489e24477b7ae40456721a53345d010373:


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
}