tenseleyflow/shithub / ce4f3a0

Browse files

S24: stale-on-push hook in push:process (opt-in via protection rule)

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
ce4f3a0d222833a56f14319fa3e00b9f9e68568c
Parents
a617ec3
Tree
8aa582d

1 changed file

StatusFile+-
M internal/worker/jobs/push_process.go 48 0
internal/worker/jobs/push_process.gomodified
@@ -16,11 +16,14 @@ import (
1616
 	"github.com/jackc/pgx/v5/pgtype"
1717
 	"github.com/jackc/pgx/v5/pgxpool"
1818
 
19
+	"github.com/tenseleyFlow/shithub/internal/checks"
1920
 	"github.com/tenseleyFlow/shithub/internal/infra/storage"
2021
 	pullsdb "github.com/tenseleyFlow/shithub/internal/pulls/sqlc"
2122
 	reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
2223
 	"github.com/tenseleyFlow/shithub/internal/worker"
2324
 	workerdb "github.com/tenseleyFlow/shithub/internal/worker/sqlc"
25
+
26
+	"path/filepath"
2427
 )
2528
 
2629
 // PushProcessDeps wires the data this handler needs.
@@ -146,6 +149,31 @@ func PushProcess(deps PushProcessDeps) worker.Handler {
146149
 			}
147150
 		}
148151
 
152
+		// 4c: stale-on-push for required checks (S24). When a head ref
153
+		// moves and the matching protection rule has
154
+		// `dismiss_stale_status_checks_on_push = true`, mark every
155
+		// not-yet-completed check suite on the previous SHA as
156
+		// (completed, conclusion='stale'). Best-effort.
157
+		if strings.HasPrefix(event.Ref, refPrefix) && !isZeroSHA(event.BeforeSha) {
158
+			branch := event.Ref[len(refPrefix):]
159
+			rules, err := rq.ListBranchProtectionRules(ctx, deps.Pool, repo.ID)
160
+			if err == nil {
161
+				rule, hasRule := longestMatchingRule(rules, branch)
162
+				if hasRule && rule.DismissStaleStatusChecksOnPush {
163
+					n, err := checks.MarkStaleForPreviousHead(ctx,
164
+						checks.Deps{Pool: deps.Pool, Logger: deps.Logger},
165
+						repo.ID, event.BeforeSha)
166
+					if err != nil {
167
+						deps.Logger.WarnContext(ctx, "push:process: mark stale checks",
168
+							"push_event_id", event.ID, "error", err)
169
+					} else if n > 0 {
170
+						deps.Logger.InfoContext(ctx, "push:process: marked check suites stale",
171
+							"push_event_id", event.ID, "count", n)
172
+					}
173
+				}
174
+			}
175
+		}
176
+
149177
 		// 5: mark processed last so a partial failure earlier triggers a
150178
 		// retry that retries the whole pipeline. Idempotency is via the
151179
 		// processed_at guard at the top.
@@ -159,6 +187,26 @@ func PushProcess(deps PushProcessDeps) worker.Handler {
159187
 	}
160188
 }
161189
 
190
+// longestMatchingRule duplicates the longest-pattern-wins matcher
191
+// from internal/repos/protection so this file doesn't take a circular
192
+// import. Same semantics: alphabetical tiebreaker, no rule = not found.
193
+func longestMatchingRule(rules []reposdb.BranchProtectionRule, branch string) (reposdb.BranchProtectionRule, bool) {
194
+	var best reposdb.BranchProtectionRule
195
+	bestLen := -1
196
+	for _, r := range rules {
197
+		ok, _ := filepath.Match(r.Pattern, branch)
198
+		if !ok {
199
+			continue
200
+		}
201
+		if len(r.Pattern) > bestLen ||
202
+			(len(r.Pattern) == bestLen && r.Pattern < best.Pattern) {
203
+			best = r
204
+			bestLen = len(r.Pattern)
205
+		}
206
+	}
207
+	return best, bestLen >= 0
208
+}
209
+
162210
 func isZeroSHA(s string) bool {
163211
 	for _, c := range s {
164212
 		if c != '0' {