mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-08 11:17:37 +00:00
4c382cea59
Fixes: https://github.com/go-gitea/gitea/issues/38818 Accepts GitHub's `$/` self-repository prefix in a reusable workflow `uses:`, alongside `./`. Gitea's `./` already resolves against the caller's own source repo and commit, which is what `$/` means, so the two are aliases here. Cycle detection folds both prefixes onto one key. Related PR for step-level support: https://gitea.com/gitea/runner/pulls/1150
80 lines
2.6 KiB
Go
80 lines
2.6 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package jobparser
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// UsesKind enumerates the supported forms of a reusable workflow "uses:" value.
|
|
type UsesKind int
|
|
|
|
const (
|
|
// UsesKindLocalSameRepo is "./<dir>/foo.yml" or "$/<dir>/foo.yml" - a path inside the calling repository.
|
|
// For example: "./.gitea/workflows/foo.yml"
|
|
UsesKindLocalSameRepo UsesKind = iota + 1
|
|
// UsesKindLocalCrossRepo is "owner/repo/<dir>/foo.yml@ref" - a workflow in another repo on the same instance.
|
|
// For example: "owner/repo/.gitea/workflows/foo.yml@ref"
|
|
UsesKindLocalCrossRepo
|
|
)
|
|
|
|
// UsesRef is the parsed form of a reusable workflow "uses:" value.
|
|
type UsesRef struct {
|
|
Kind UsesKind
|
|
Owner string // empty for UsesKindLocalSameRepo
|
|
Repo string // empty for UsesKindLocalSameRepo
|
|
Path string // workflow file path inside the source repo
|
|
Ref string // git ref; empty for UsesKindLocalSameRepo
|
|
}
|
|
|
|
var (
|
|
reLocalSameRepo = regexp.MustCompile(`^[.$]/([^@]+\.ya?ml)$`)
|
|
reLocalCrossRepo = regexp.MustCompile(`^([-.\w]+)/([-.\w]+)/([^@]+\.ya?ml)@(.+)$`)
|
|
)
|
|
|
|
// ParseUses parses the SYNTAX of a reusable workflow "uses:" value into a UsesRef. Two forms are supported:
|
|
// - "./<dir>/foo.yml" or "$/<dir>/foo.yml" (UsesKindLocalSameRepo, no @ref)
|
|
// - "OWNER/REPO/<dir>/foo.yml@REF" (UsesKindLocalCrossRepo)
|
|
//
|
|
// It deliberately does NOT validate that <dir> is an allowed workflow directory: the allowed directories are instance-configurable (WORKFLOW_DIRS / SCOPED_WORKFLOW_DIRS).
|
|
// The caller (services/actions.ResolveUses) enforces the directory allowlist. The returned Path is the cleaned, repo-relative file path.
|
|
func ParseUses(s string) (*UsesRef, error) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return nil, errors.New("empty uses value")
|
|
}
|
|
|
|
if strings.HasPrefix(s, "./") || strings.HasPrefix(s, "$/") {
|
|
m := reLocalSameRepo.FindStringSubmatch(s)
|
|
if m == nil {
|
|
return nil, fmt.Errorf(`invalid local "uses:" %q (expect ./<dir>/<file>.yml or $/<dir>/<file>.yml)`, s)
|
|
}
|
|
p := m[1]
|
|
if path.Clean(p) != p {
|
|
return nil, fmt.Errorf("invalid workflow path %q", s)
|
|
}
|
|
return &UsesRef{Kind: UsesKindLocalSameRepo, Path: p}, nil
|
|
}
|
|
|
|
m := reLocalCrossRepo.FindStringSubmatch(s)
|
|
if m == nil {
|
|
return nil, fmt.Errorf(`invalid cross-repo "uses:" %q (expect owner/repo/<dir>/<file>.yml@ref)`, s)
|
|
}
|
|
p := m[3]
|
|
if path.Clean(p) != p {
|
|
return nil, fmt.Errorf("invalid workflow path %q", s)
|
|
}
|
|
return &UsesRef{
|
|
Kind: UsesKindLocalCrossRepo,
|
|
Owner: m[1],
|
|
Repo: m[2],
|
|
Path: p,
|
|
Ref: m[4],
|
|
}, nil
|
|
}
|