internal/store/statuses.go
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}