package webhook import ( "fmt" "path" "strings" "github.com/alexei/tinyforge/internal/store" ) // matchStage finds the first stage of a project whose tag pattern matches the // given tag. Uses path.Match for glob-style matching (same as the registry poller). func matchStage(st *store.Store, projectID, tag string) (store.Stage, bool, error) { stages, err := st.GetStagesByProjectID(projectID) if err != nil { return store.Stage{}, false, fmt.Errorf("get stages: %w", err) } for _, stage := range stages { pattern := stage.TagPattern if pattern == "" { pattern = "*" } matched, err := path.Match(pattern, tag) if err != nil { // Invalid pattern — skip this stage. continue } if matched { return stage, true, nil } } return store.Stage{}, false, nil } // imageMatches reports whether an incoming image reference matches the // project's stored image. The comparison is case-sensitive and exact. func imageMatches(projectImage, incomingImage string) bool { return projectImage == incomingImage } // siteRefMatches reports whether a Git ref (e.g. "refs/heads/main" or // "refs/tags/v1.2.3") targets the site's configured branch or tag pattern. // // For sync_trigger = "push": the ref must be a heads/ ref whose // branch name equals site.Branch. // For sync_trigger = "tag": the ref must be a tags/ ref whose tag name // matches site.TagPattern via glob semantics. // Unknown triggers return false (caller should have filtered these out). func siteRefMatches(site store.StaticSite, ref string) bool { switch site.SyncTrigger { case "push": branch, ok := strings.CutPrefix(ref, "refs/heads/") if !ok { return false } if site.Branch == "" { return true } return branch == site.Branch case "tag": tag, ok := strings.CutPrefix(ref, "refs/tags/") if !ok { return false } pattern := site.TagPattern if pattern == "" { pattern = "*" } matched, err := path.Match(pattern, tag) if err != nil { return false } return matched default: return false } }