refactor(glob): use strings.Builder for regexp compilation (#37730)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wxiaoguang <2114189+wxiaoguang@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Copilot
2026-05-16 11:10:53 +00:00
committed by GitHub
co-authored by copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> wxiaoguang wxiaoguang
parent 96e0dc15a3
commit 54ff68b0a9
+43 -37
View File
@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"slices" "slices"
"strings"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@@ -29,16 +30,17 @@ type globCompiler struct {
globPattern []rune globPattern []rune
regexpPattern string regexpPattern string
regexp *regexp.Regexp regexp *regexp.Regexp
builder *strings.Builder
pos int pos int
negativeFlip bool negativeFlip bool
} }
// compileChars compiles character class patterns like [abc] or [!abc] // compileChars compiles character class patterns like [abc] or [!abc]
func (g *globCompiler) compileChars() (string, error) { func (g *globCompiler) compileChars() error {
result := "" g.builder.WriteByte('[')
if g.pos < len(g.globPattern) && g.globPattern[g.pos] == '!' { if g.pos < len(g.globPattern) && g.globPattern[g.pos] == '!' {
g.pos++ g.pos++
result += "^" g.builder.WriteByte('^')
} }
for g.pos < len(g.globPattern) { for g.pos < len(g.globPattern) {
@@ -46,33 +48,34 @@ func (g *globCompiler) compileChars() (string, error) {
g.pos++ g.pos++
if c == ']' { if c == ']' {
return "[" + result + "]", nil g.builder.WriteByte(']')
return nil
} }
if c == '\\' { if c == '\\' {
if g.pos >= len(g.globPattern) { if g.pos >= len(g.globPattern) {
return "", errors.New("unterminated character class escape") return errors.New("unterminated character class escape")
} }
result += "\\" + string(g.globPattern[g.pos]) g.builder.WriteByte('\\')
g.builder.WriteRune(g.globPattern[g.pos])
g.pos++ g.pos++
} else { } else {
result += string(c) g.builder.WriteRune(c)
} }
} }
return "", errors.New("unterminated character class") return errors.New("unterminated character class")
} }
// compile compiles the glob pattern into a regular expression // compile compiles the glob pattern into a regular expression
func (g *globCompiler) compile(subPattern bool) (string, error) { func (g *globCompiler) compile(subPattern bool) error {
result := ""
for g.pos < len(g.globPattern) { for g.pos < len(g.globPattern) {
c := g.globPattern[g.pos] c := g.globPattern[g.pos]
g.pos++ g.pos++
if subPattern && c == '}' { if subPattern && c == '}' {
return "(" + result + ")", nil g.builder.WriteByte(')')
return nil
} }
switch c { switch c {
@@ -95,59 +98,61 @@ func (g *globCompiler) compile(subPattern bool) (string, error) {
} else { } else {
g.pos++ g.pos++
} }
result += ".*" // match any sequence of characters g.builder.WriteString(".*") // match any sequence of characters
} else { } else {
result += g.nonSeparatorChars + "*" // match any sequence of non-separator characters g.builder.WriteString(g.nonSeparatorChars)
g.builder.WriteByte('*') // match any sequence of non-separator characters
} }
case '?': case '?':
if g.regexpQuestion { if g.regexpQuestion {
result += "?" g.builder.WriteByte('?')
} else { } else {
result += g.nonSeparatorChars // match any single non-separator character g.builder.WriteString(g.nonSeparatorChars) // match any single non-separator character
} }
case '+': case '+':
if g.regexpPlus { if g.regexpPlus {
result += "+" g.builder.WriteByte('+')
} else { } else {
result += "\\" + string(c) g.builder.WriteByte('\\')
g.builder.WriteRune(c)
} }
case '[': case '[':
chars, err := g.compileChars() if err := g.compileChars(); err != nil {
if err != nil { return err
return "", err
} }
result += chars
case '{': case '{':
subResult, err := g.compile(true) g.builder.WriteByte('(')
if err != nil { if err := g.compile(true); err != nil {
return "", err return err
} }
result += subResult
case ',': case ',':
if subPattern { if subPattern {
result += "|" g.builder.WriteByte('|')
} else { } else {
result += "," g.builder.WriteByte(',')
} }
case '\\': case '\\':
if g.pos >= len(g.globPattern) { if g.pos >= len(g.globPattern) {
return "", errors.New("no character to escape") return errors.New("no character to escape")
} }
result += "\\" + string(g.globPattern[g.pos]) g.builder.WriteByte('\\')
g.builder.WriteRune(g.globPattern[g.pos])
g.pos++ g.pos++
case '.', '^', '$', '(', ')', '|': case '.', '^', '$', '(', ')', '|':
result += "\\" + string(c) // escape regexp special characters g.builder.WriteByte('\\')
g.builder.WriteRune(c) // escape regexp special characters
default: default:
result += string(c) g.builder.WriteRune(c)
} }
} }
return result, nil return nil
} }
func initGlobCompiler(g *globCompiler, pattern string, separators []rune) (Glob, error) { func initGlobCompiler(g *globCompiler, pattern string, separators []rune) (Glob, error) {
g.globPattern = []rune(pattern) g.globPattern = []rune(pattern)
g.separators = separators g.separators = separators
g.builder = new(strings.Builder)
// Escape separators for use in character class // Escape separators for use in character class
escapedSeparators := regexp.QuoteMeta(string(separators)) escapedSeparators := regexp.QuoteMeta(string(separators))
@@ -162,12 +167,13 @@ func initGlobCompiler(g *globCompiler, pattern string, separators []rune) (Glob,
g.pos++ g.pos++
} }
compiled, err := g.compile(false) g.builder.WriteByte('^')
if err != nil { if err := g.compile(false); err != nil {
return nil, err return nil, err
} }
g.builder.WriteByte('$')
g.regexpPattern = "^" + compiled + "$" g.regexpPattern = g.builder.String()
g.builder = nil
regex, err := regexp.Compile(g.regexpPattern) regex, err := regexp.Compile(g.regexpPattern)
if err != nil { if err != nil {