tenseleyflow/shithub / 4dc0c5d

Browse files

S24: settings UI for required-check names + dismiss-stale-on-push

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
4dc0c5d24460d6b0c8d085b1c2e18deea28b427b
Parents
ce4f3a0
Tree
7955ead

2 changed files

StatusFile+-
M internal/web/handlers/repo/settings_branches.go 44 0
M internal/web/templates/repo/settings_branches.html 5 1
internal/web/handlers/repo/settings_branches.gomodified
@@ -81,6 +81,12 @@ func (h *Handlers) settingsBranchesUpsert(w http.ResponseWriter, r *http.Request
8181
 	}
8282
 	dismissStale := r.PostFormValue("dismiss_stale_reviews_on_push") == "on"
8383
 
84
+	// S24 required-status-check names: comma-separated input. Empty
85
+	// list means no required checks. Names are matched verbatim
86
+	// against `check_runs.name` at gate time.
87
+	requiredChecks := splitCommaList(r.PostFormValue("required_status_check_names"))
88
+	dismissStaleChecks := r.PostFormValue("dismiss_stale_status_checks_on_push") == "on"
89
+
8490
 	allowed, err := resolveUsernameList(r, h, r.PostFormValue("allowed_pushers"))
8591
 	if err != nil {
8692
 		http.Error(w, err.Error(), http.StatusBadRequest)
@@ -114,6 +120,13 @@ func (h *Handlers) settingsBranchesUpsert(w http.ResponseWriter, r *http.Request
114120
 		}); err != nil {
115121
 			h.d.Logger.WarnContext(r.Context(), "branch-protection: review settings", "error", err)
116122
 		}
123
+		if err := h.rq.UpdateBranchProtectionCheckSettings(r.Context(), h.d.Pool, reposdb.UpdateBranchProtectionCheckSettingsParams{
124
+			ID:                              newID,
125
+			StatusChecksRequired:            requiredChecks,
126
+			DismissStaleStatusChecksOnPush:  dismissStaleChecks,
127
+		}); err != nil {
128
+			h.d.Logger.WarnContext(r.Context(), "branch-protection: check settings", "error", err)
129
+		}
117130
 		_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
118131
 			audit.ActionRepoCreated, audit.TargetRepo, row.ID,
119132
 			map[string]any{"branch_protection_rule_id": newID, "pattern": pattern, "action": "create"})
@@ -149,6 +162,13 @@ func (h *Handlers) settingsBranchesUpsert(w http.ResponseWriter, r *http.Request
149162
 		}); err != nil {
150163
 			h.d.Logger.WarnContext(r.Context(), "branch-protection: review settings", "error", err)
151164
 		}
165
+		if err := h.rq.UpdateBranchProtectionCheckSettings(r.Context(), h.d.Pool, reposdb.UpdateBranchProtectionCheckSettingsParams{
166
+			ID:                              id,
167
+			StatusChecksRequired:            requiredChecks,
168
+			DismissStaleStatusChecksOnPush:  dismissStaleChecks,
169
+		}); err != nil {
170
+			h.d.Logger.WarnContext(r.Context(), "branch-protection: check settings", "error", err)
171
+		}
152172
 		_ = h.d.Audit.Record(r.Context(), h.d.Pool, viewer.ID,
153173
 			audit.ActionRepoCreated, audit.TargetRepo, row.ID,
154174
 			map[string]any{"branch_protection_rule_id": id, "pattern": pattern, "action": "update"})
@@ -245,6 +265,30 @@ func (h *Handlers) settingsDefaultBranch(w http.ResponseWriter, r *http.Request)
245265
 	http.Redirect(w, r, "/"+owner.Username+"/"+row.Name+"/settings/branches?notice=default-changed", http.StatusSeeOther)
246266
 }
247267
 
268
+// splitCommaList parses a comma-separated string into a deduplicated,
269
+// trimmed slice. Empty entries drop. Used by S24's required-check
270
+// name input on the protection-rule form.
271
+func splitCommaList(raw string) []string {
272
+	raw = strings.TrimSpace(raw)
273
+	if raw == "" {
274
+		return []string{}
275
+	}
276
+	seen := map[string]struct{}{}
277
+	out := []string{}
278
+	for _, p := range strings.Split(raw, ",") {
279
+		name := strings.TrimSpace(p)
280
+		if name == "" {
281
+			continue
282
+		}
283
+		if _, dup := seen[name]; dup {
284
+			continue
285
+		}
286
+		seen[name] = struct{}{}
287
+		out = append(out, name)
288
+	}
289
+	return out
290
+}
291
+
248292
 // resolveUsernameList parses a comma-separated username list and
249293
 // resolves each to a user_id. Empty input returns an empty slice
250294
 // (no allowed-pushers restriction). Unknown usernames produce an
internal/web/templates/repo/settings_branches.htmlmodified
@@ -36,7 +36,7 @@
3636
           <td>{{ if .PreventForcePush }}🚫{{ else }}allowed{{ end }}</td>
3737
           <td>{{ if .PreventDeletion }}🚫{{ else }}allowed{{ end }}</td>
3838
           <td>{{ len .AllowedPusherUserIds }} {{ if eq (len .AllowedPusherUserIds) 1 }}user{{ else }}users{{ end }}</td>
39
-          <td>{{ .RequiredReviewCount }}{{ if .DismissStaleReviewsOnPush }} · dismiss-stale{{ end }}</td>
39
+          <td>{{ .RequiredReviewCount }}{{ if .DismissStaleReviewsOnPush }} · dismiss-stale{{ end }}{{ if .StatusChecksRequired }} · checks: {{ range $i, $n := .StatusChecksRequired }}{{ if $i }}, {{ end }}<code>{{ $n }}</code>{{ end }}{{ end }}</td>
4040
           <td>
4141
             <form method="POST" action="/{{ $.Owner }}/{{ $.Repo.Name }}/settings/branches/{{ .ID }}/delete" style="display:inline">
4242
               <input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
@@ -69,6 +69,10 @@
6969
         <input type="number" name="required_review_count" min="0" value="0">
7070
       </label>
7171
       <label><input type="checkbox" name="dismiss_stale_reviews_on_push"> Dismiss stale reviews on new push (S23)</label>
72
+      <label>Required status checks (S24) — comma-separated check-run names that must succeed on the head SHA
73
+        <input type="text" name="required_status_check_names" placeholder="lint, unit-tests">
74
+      </label>
75
+      <label><input type="checkbox" name="dismiss_stale_status_checks_on_push"> Mark in-flight checks stale on new push (S24)</label>
7276
       <button type="submit" class="shithub-button shithub-button-primary">Create rule</button>
7377
     </form>
7478
   </section>