internal/store/statuses.go

eec36526d2721d98ac0d9446ba4e2975d16b5d8b
gitbay/internal/store/statuses.go history · blame · raw

108 lines · 3347 bytes

  1package store
  2
  3import "strings"
  4
  5type CommitStatus struct {
  6	Context     string
  7	State       string // pending | success | failure | error
  8	Description string
  9	TargetURL   string
 10	Creator     string
 11	UpdatedAt   string
 12}
 13
 14// SetCommitStatus upserts the latest state for one context on one commit.
 15// A zero creatorID records no creator (system actions like the scheduler).
 16func (s *Store) SetCommitStatus(repoID int64, sha, context, state, description, targetURL string, creatorID int64) error {
 17	var creator any
 18	if creatorID != 0 {
 19		creator = creatorID
 20	}
 21	_, err := s.DB.Exec(`
 22		INSERT INTO commit_statuses (repo_id, commit_sha, context, state, description, target_url, creator_id)
 23		VALUES (?, ?, ?, ?, ?, ?, ?)
 24		ON CONFLICT (repo_id, commit_sha, context) DO UPDATE SET
 25			state = excluded.state, description = excluded.description,
 26			target_url = excluded.target_url, creator_id = excluded.creator_id,
 27			updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')`,
 28		repoID, sha, context, state, description, targetURL, creator)
 29	return err
 30}
 31
 32// ListCommitStatuses returns the latest status per context for a commit.
 33func (s *Store) ListCommitStatuses(repoID int64, sha string) ([]CommitStatus, error) {
 34	rows, err := s.DB.Query(`
 35		SELECT cs.context, cs.state, cs.description, cs.target_url, COALESCE(u.username, ''), cs.updated_at
 36		FROM commit_statuses cs LEFT JOIN users u ON u.id = cs.creator_id
 37		WHERE cs.repo_id = ? AND cs.commit_sha = ? ORDER BY cs.context`, repoID, sha)
 38	if err != nil {
 39		return nil, err
 40	}
 41	defer rows.Close()
 42	var out []CommitStatus
 43	for rows.Next() {
 44		var c CommitStatus
 45		if err := rows.Scan(&c.Context, &c.State, &c.Description, &c.TargetURL, &c.Creator, &c.UpdatedAt); err != nil {
 46			return nil, err
 47		}
 48		out = append(out, c)
 49	}
 50	return out, rows.Err()
 51}
 52
 53// CombinedStatus reduces per-context states to one: error/failure dominate,
 54// then pending, then success; "" when no statuses exist.
 55// CombinedStatusFor returns the combined state for each of several commits
 56// in one query. The log lists fifty commits at a time; asking per commit
 57// turns one page into fifty round trips.
 58func (s *Store) CombinedStatusFor(repoID int64, shas []string) (map[string]string, error) {
 59	out := map[string]string{}
 60	if len(shas) == 0 {
 61		return out, nil
 62	}
 63	args := make([]any, 0, len(shas)+1)
 64	args = append(args, repoID)
 65	for _, sha := range shas {
 66		args = append(args, sha)
 67	}
 68	q := `SELECT commit_sha, state FROM commit_statuses WHERE repo_id = ? AND commit_sha IN (?` +
 69		strings.Repeat(",?", len(shas)-1) + `)`
 70	rows, err := s.DB.Query(q, args...)
 71	if err != nil {
 72		return nil, err
 73	}
 74	defer rows.Close()
 75	for rows.Next() {
 76		var sha, state string
 77		if err := rows.Scan(&sha, &state); err != nil {
 78			return nil, err
 79		}
 80		// Same precedence as CombinedStatus, folded in as rows arrive.
 81		switch {
 82		case out[sha] == "failure":
 83		case state == "error" || state == "failure":
 84			out[sha] = "failure"
 85		case state == "pending":
 86			out[sha] = "pending"
 87		case out[sha] == "":
 88			out[sha] = "success"
 89		}
 90	}
 91	return out, rows.Err()
 92}
 93
 94func CombinedStatus(statuses []CommitStatus) string {
 95	if len(statuses) == 0 {
 96		return ""
 97	}
 98	combined := "success"
 99	for _, s := range statuses {
100		switch s.State {
101		case "error", "failure":
102			return "failure"
103		case "pending":
104			combined = "pending"
105		}
106	}
107	return combined
108}