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"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -233,6 +234,10 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
|
|||||||
push.Repository.FullName, branch, push.Pusher.Login, len(push.Commits))
|
push.Repository.FullName, branch, push.Pusher.Login, len(push.Commits))
|
||||||
|
|
||||||
changedFiles := collectChangedFiles(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)
|
workflows, err := fetchWorkflows(c.GiteaURL, c.GiteaToken, push.Repository.FullName, branch)
|
||||||
if err != nil || len(workflows) == 0 {
|
if err != nil || len(workflows) == 0 {
|
||||||
@@ -247,7 +252,7 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !shouldTrigger(wf, branch, changedFiles) {
|
if !shouldTrigger(wf, content, branch, changedFiles, commitMessages) {
|
||||||
log.Printf("Skipping %s (no matching trigger)", name)
|
log.Printf("Skipping %s (no matching trigger)", name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -633,7 +638,7 @@ func fetchRawFile(baseURL, token, repoFullName, path, branch string) (string, er
|
|||||||
|
|
||||||
// --- Trigger evaluation ---
|
// --- 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{})
|
onMap, ok := wf.On.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
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
|
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 {
|
func matchPath(pattern, file string) bool {
|
||||||
if strings.HasSuffix(pattern, "/**") {
|
if strings.HasSuffix(pattern, "/**") {
|
||||||
prefix := strings.TrimSuffix(pattern, "/**")
|
prefix := strings.TrimSuffix(pattern, "/**")
|
||||||
|
|||||||
Reference in new issue
Block a user