// Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package actions import ( "context" "fmt" git_model "gitea.dev/models/git" repo_model "gitea.dev/models/repo" actions_module "gitea.dev/modules/actions" "gitea.dev/modules/gitrepo" "gitea.dev/modules/log" lru "github.com/hashicorp/golang-lru/v2" ) // cachedScopedWorkflows is one source repo's parsed scoped workflows together with the default-branch SHA they were parsed at. type cachedScopedWorkflows struct { sha string parsed []*actions_module.ParsedScopedWorkflow } // scopedWorkflowCache caches each scoped-workflow source repo's parsed workflows, keyed by source repo ID. // There is exactly one entry per source: a default-branch update is detected by SHA mismatch and overwrites the entry, so stale parses never accumulate. var scopedWorkflowCache *lru.Cache[int64, *cachedScopedWorkflows] const defaultScopedWorkflowCacheSize = 1024 func init() { c, err := lru.New[int64, *cachedScopedWorkflows](defaultScopedWorkflowCacheSize) if err != nil { log.Fatal("failed to new scopedWorkflowCache, err: %v", err) } scopedWorkflowCache = c } // LoadParsedScopedWorkflows returns the source repo's parsed scoped workflows at its current default-branch HEAD. func LoadParsedScopedWorkflows(ctx context.Context, sourceRepo *repo_model.Repository) (sha string, parsed []*actions_module.ParsedScopedWorkflow, err error) { branch, err := git_model.GetBranch(ctx, sourceRepo.ID, sourceRepo.DefaultBranch) if err != nil { return "", nil, fmt.Errorf("get source default branch: %w", err) } sha = branch.CommitID if v, ok := scopedWorkflowCache.Get(sourceRepo.ID); ok && v.sha == sha { // cache hit at the current default-branch HEAD return sha, v.parsed, nil } // cache miss: open the source repo at the exact SHA we keyed on sourceGitRepo, err := gitrepo.OpenRepository(ctx, sourceRepo) if err != nil { return "", nil, fmt.Errorf("open source repo: %w", err) } defer sourceGitRepo.Close() sourceCommit, err := sourceGitRepo.GetCommit(sha) if err != nil { return "", nil, fmt.Errorf("get source commit %s: %w", sha, err) } parsed, err = actions_module.ParseScopedWorkflows(sourceCommit) if err != nil { return "", nil, err } // overwrite this source's single entry (a stale entry from a previous HEAD is replaced, not accumulated) scopedWorkflowCache.Add(sourceRepo.ID, &cachedScopedWorkflows{sha: sha, parsed: parsed}) return sha, parsed, nil } // ScopedWorkflowContent returns one scoped workflow's raw content (by entry name) at the source repo's current default-branch HEAD, or nil if no such workflow exists there. func ScopedWorkflowContent(ctx context.Context, sourceRepo *repo_model.Repository, entryName string) ([]byte, error) { _, parsed, err := LoadParsedScopedWorkflows(ctx, sourceRepo) if err != nil { return nil, err } for _, p := range parsed { if p.EntryName == entryName { return p.Content, nil } } return nil, nil }