The forge's contents and diffpatch endpoints take a branch, never a commit to
build on, so the parent is read from the commit the forge made, and a branch
that moved under the commit is said. Blobs are read at the commit read first,
not at the branch name. Protection rules match as Gitea 1.27 matches them
([...], {a,b}, escapes, a plain name in any case), in the Go bundle and in
guard.ts alike. One commit carries at most 4 MiB per file or patch and 16 MiB
together; code search stops at its deadline and says so. The HTTP client the
two Go bundles copied is one package, internal/forgehttp.
117 lines
2.7 KiB
Go
117 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// ruleCovers is whether a protection rule's name covers a branch, as the forge itself decides it (Gitea 1.27,
|
|
// models/git/protected_branch.go Match and modules/glob, novox/hq issue 539): a name without a glob character
|
|
// covers the branch of that name in any case; any other name is a glob with `/` as its separator — `*` any
|
|
// run within a segment, `**` any run across them, `?` one character of a segment, `[abc]`, `[a-z]` and `[!abc]`
|
|
// one character, `{a,b}` either, `\` the next character itself — matched case-sensitively; a glob the forge
|
|
// cannot compile covers its own text exactly. The TypeScript bundle's ruleCovers (guard.ts) is the same rule.
|
|
func ruleCovers(rule, branch string) bool {
|
|
if !strings.ContainsAny(rule, `*?\[]{}`) {
|
|
return strings.EqualFold(rule, branch)
|
|
}
|
|
re, err := globRegexp(rule)
|
|
if err != nil {
|
|
return rule == branch
|
|
}
|
|
return re.MatchString(branch)
|
|
}
|
|
|
|
// globRegexp is the forge's glob as a regular expression, compiled as modules/glob compiles it with `/` as
|
|
// the separator.
|
|
func globRegexp(glob string) (*regexp.Regexp, error) {
|
|
g := []rune(glob)
|
|
var b strings.Builder
|
|
pos := 0
|
|
var compile func(sub bool) error
|
|
chars := func() error {
|
|
b.WriteByte('[')
|
|
if pos < len(g) && g[pos] == '!' {
|
|
pos++
|
|
b.WriteByte('^')
|
|
}
|
|
for pos < len(g) {
|
|
c := g[pos]
|
|
pos++
|
|
switch {
|
|
case c == ']':
|
|
b.WriteByte(']')
|
|
return nil
|
|
case c == '\\':
|
|
if pos >= len(g) {
|
|
return errors.New("unterminated character class escape")
|
|
}
|
|
b.WriteByte('\\')
|
|
b.WriteRune(g[pos])
|
|
pos++
|
|
default:
|
|
b.WriteRune(c)
|
|
}
|
|
}
|
|
return errors.New("unterminated character class")
|
|
}
|
|
compile = func(sub bool) error {
|
|
for pos < len(g) {
|
|
c := g[pos]
|
|
pos++
|
|
if sub && c == '}' {
|
|
b.WriteByte(')')
|
|
return nil
|
|
}
|
|
switch c {
|
|
case '*':
|
|
if pos < len(g) && g[pos] == '*' {
|
|
pos++
|
|
b.WriteString(".*")
|
|
} else {
|
|
b.WriteString("[^/]*")
|
|
}
|
|
case '?':
|
|
b.WriteString("[^/]")
|
|
case '+':
|
|
b.WriteString(`\+`)
|
|
case '[':
|
|
if err := chars(); err != nil {
|
|
return err
|
|
}
|
|
case '{':
|
|
b.WriteByte('(')
|
|
if err := compile(true); err != nil {
|
|
return err
|
|
}
|
|
case ',':
|
|
if sub {
|
|
b.WriteByte('|')
|
|
} else {
|
|
b.WriteByte(',')
|
|
}
|
|
case '\\':
|
|
if pos >= len(g) {
|
|
return errors.New("no character to escape")
|
|
}
|
|
b.WriteByte('\\')
|
|
b.WriteRune(g[pos])
|
|
pos++
|
|
case '.', '^', '$', '(', ')', '|':
|
|
b.WriteByte('\\')
|
|
b.WriteRune(c)
|
|
default:
|
|
b.WriteRune(c)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
b.WriteByte('^')
|
|
if err := compile(false); err != nil {
|
|
return nil, err
|
|
}
|
|
b.WriteByte('$')
|
|
return regexp.Compile(b.String())
|
|
}
|