feat(dispatch): commit-marker gate — # tinqs-marker: <regex> in a workflow requires a matching pushed commit message
Opt-in per workflow via a YAML comment; no comment = unchanged behaviour for every repo. Marker-less pushes spawn no runner at all (no run queue, no waste). Added for ariki-game sim-publish-staging (Ozan: deploys are explicit). Co-Authored-By: Ozan Bozkurt <ozan@tinqs.com> Co-Authored-By: ZCode GLM-5.3-Flash <agents@tinqs.com>
This commit is contained in:
1 parent
33f967e42e
commit
176c2031c7
1 file changed
+41
-2
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -233,6 +234,10 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
|
||||
push.Repository.FullName, branch, push.Pusher.Login, len(push.Commits))
|
||||
|
||||
changedFiles := collectChangedFiles(push.Commits)
|
||||
var commitMessages []string
|
||||
for _, c := range push.Commits {
|
||||
commitMessages = append(commitMessages, c.Message)
|
||||
}
|
||||
|
||||
workflows, err := fetchWorkflows(c.GiteaURL, c.GiteaToken, push.Repository.FullName, branch)
|
||||
if err != nil || len(workflows) == 0 {
|
||||
@@ -247,7 +252,7 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
|
||||
continue
|
||||
}
|
||||
|
||||
if !shouldTrigger(wf, branch, changedFiles) {
|
||||
if !shouldTrigger(wf, content, branch, changedFiles, commitMessages) {
|
||||
log.Printf("Skipping %s (no matching trigger)", name)
|
||||
continue
|
||||
}
|
||||
@@ -633,7 +638,7 @@ func fetchRawFile(baseURL, token, repoFullName, path, branch string) (string, er
|
||||
|
||||
// --- Trigger evaluation ---
|
||||
|
||||
func shouldTrigger(wf Workflow, branch string, changedFiles []string) bool {
|
||||
func shouldTrigger(wf Workflow, wfContent string, branch string, changedFiles []string, commitMessages []string) bool {
|
||||
onMap, ok := wf.On.(map[string]interface{})
|
||||
if !ok {
|
||||
return true
|
||||
@@ -701,9 +706,43 @@ func shouldTrigger(wf Workflow, branch string, changedFiles []string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Opt-in commit-marker gate (Ozan 2026-09-22): a `# tinqs-marker: <regex>` comment in the
|
||||
// workflow makes push triggers require a pushed commit whose MESSAGE matches the regex
|
||||
// (case-insensitive). No match → no runner, nothing spawned. workflow_dispatch events
|
||||
// never reach this function, so button/CLI publishes are unaffected.
|
||||
if mr := markerRegex(wfContent); mr != nil {
|
||||
matched := false
|
||||
for _, msg := range commitMessages {
|
||||
if mr.MatchString(msg) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
log.Printf("Marker gate: no pushed commit matches %s — not spawning", mr)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// markerRegex extracts the opt-in marker regex from a `# tinqs-marker: <regex>` comment
|
||||
// in the workflow YAML. Empty (no comment) = no marker gating — behaviour unchanged.
|
||||
func markerRegex(content string) *regexp.Regexp {
|
||||
re := regexp.MustCompile(`(?im)^\s*#\s*tinqs-marker:\s*(.+)$`)
|
||||
m := re.FindStringSubmatch(content)
|
||||
if len(m) < 2 {
|
||||
return nil
|
||||
}
|
||||
cr, err := regexp.Compile(strings.TrimSpace(m[1]))
|
||||
if err != nil {
|
||||
log.Printf("Bad tinqs-marker regex %q: %v", m[1], err)
|
||||
return nil
|
||||
}
|
||||
return cr
|
||||
}
|
||||
|
||||
func matchPath(pattern, file string) bool {
|
||||
if strings.HasSuffix(pattern, "/**") {
|
||||
prefix := strings.TrimSuffix(pattern, "/**")
|
||||
|
||||
Reference in new issue
Block a user