detached 5a16c3d

refactor(executor): compile the hashFiles regexps once

The glob code compiled the same three regular expressions on every call. They
are package variables now.
commit
5a16c3defabaa5489ee367485fd39537b035b02e
author
brice · · 2026-10-13 04:04 UTC
parent
c9a800a
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +11 -5
M internal/executor/hashfiles.go +11 -5
@@ -109,7 +109,7 @@ const (
109109 )
110110
111111 func splitSlash(s string) []string {
112- return regexp.MustCompile(`/+`).Split(s, -1)
112+ return slashRunRe.Split(s, -1)
113113 }
114114
115115 var errPattern = errors.New("invalid pattern")
@@ -126,7 +126,7 @@ func newGlobPattern(text, cwd, home string) (*globPattern, error) {
126126 if text == "" {
127127 return nil, fmt.Errorf("%w: pattern cannot be empty", errPattern)
128128 }
129- norm := regexp.MustCompile(`//+`).ReplaceAllString(text, "/")
129+ norm := doubleSlashRe.ReplaceAllString(text, "/")
130130 p.trailingSep = strings.HasSuffix(norm, "/")
131131 trimmed := norm
132132 if trimmed != "/" {
@@ -266,6 +266,12 @@ func getLiteral(s string) string {
266266 return string(lit)
267267 }
268268
269+var (
270+ slashRunRe = regexp.MustCompile(`/+`)
271+ doubleSlashRe = regexp.MustCompile(`//+`)
272+ neverMatchesRe = regexp.MustCompile(`$.`)
273+)
274+
269275 const reSpecials = "().*{}+?[]^$\\!"
270276
271277 // parseMM is minimatch's parse for one segment with the options of
@@ -283,7 +289,7 @@ func parseMM(s string) mmSeg {
283289 }
284290 rx, err := regexp.Compile("^" + re + "$")
285291 if err != nil {
286- return mmSeg{re: regexp.MustCompile(`$.`)}
292+ return mmSeg{re: neverMatchesRe}
287293 }
288294 return mmSeg{re: rx}
289295 }
@@ -395,7 +401,7 @@ func translateMM(p []rune) (re string, magic bool) {
395401 // match is Pattern.match.
396402 func (p *globPattern) match(item string) int {
397403 if p.segs[len(p.segs)-1].raw == "**" {
398- item = regexp.MustCompile(`//+`).ReplaceAllString(item, "/")
404+ item = doubleSlashRe.ReplaceAllString(item, "/")
399405 if !strings.HasSuffix(item, "/") && !p.implicit {
400406 item += "/"
401407 }
@@ -429,7 +435,7 @@ func (p *globPattern) partialMatch(item string) bool {
429435 }
430436
431437 func trimTrailingSep(s string) string {
432- s = regexp.MustCompile(`//+`).ReplaceAllString(s, "/")
438+ s = doubleSlashRe.ReplaceAllString(s, "/")
433439 if s != "/" && strings.HasSuffix(s, "/") {
434440 return s[:len(s)-1]
435441 }
@@ -109,7 +109,7 @@ const (
109 )109 )
110 110
111 func splitSlash(s string) []string {111 func splitSlash(s string) []string {
112- return regexp.MustCompile(`/+`).Split(s, -1)112+ return slashRunRe.Split(s, -1)
113 }113 }
114 114
115 var errPattern = errors.New("invalid pattern")115 var errPattern = errors.New("invalid pattern")
@@ -126,7 +126,7 @@ func newGlobPattern(text, cwd, home string) (*globPattern, error) {
126 if text == "" {126 if text == "" {
127 return nil, fmt.Errorf("%w: pattern cannot be empty", errPattern)127 return nil, fmt.Errorf("%w: pattern cannot be empty", errPattern)
128 }128 }
129- norm := regexp.MustCompile(`//+`).ReplaceAllString(text, "/")129+ norm := doubleSlashRe.ReplaceAllString(text, "/")
130 p.trailingSep = strings.HasSuffix(norm, "/")130 p.trailingSep = strings.HasSuffix(norm, "/")
131 trimmed := norm131 trimmed := norm
132 if trimmed != "/" {132 if trimmed != "/" {
@@ -266,6 +266,12 @@ func getLiteral(s string) string {
266 return string(lit)266 return string(lit)
267 }267 }
268 268
269+var (
270+ slashRunRe = regexp.MustCompile(`/+`)
271+ doubleSlashRe = regexp.MustCompile(`//+`)
272+ neverMatchesRe = regexp.MustCompile(`$.`)
273+)
274+
269 const reSpecials = "().*{}+?[]^$\\!"275 const reSpecials = "().*{}+?[]^$\\!"
270 276
271 // parseMM is minimatch's parse for one segment with the options of277 // parseMM is minimatch's parse for one segment with the options of
@@ -283,7 +289,7 @@ func parseMM(s string) mmSeg {
283 }289 }
284 rx, err := regexp.Compile("^" + re + "$")290 rx, err := regexp.Compile("^" + re + "$")
285 if err != nil {291 if err != nil {
286- return mmSeg{re: regexp.MustCompile(`$.`)}292+ return mmSeg{re: neverMatchesRe}
287 }293 }
288 return mmSeg{re: rx}294 return mmSeg{re: rx}
289 }295 }
@@ -395,7 +401,7 @@ func translateMM(p []rune) (re string, magic bool) {
395 // match is Pattern.match.401 // match is Pattern.match.
396 func (p *globPattern) match(item string) int {402 func (p *globPattern) match(item string) int {
397 if p.segs[len(p.segs)-1].raw == "**" {403 if p.segs[len(p.segs)-1].raw == "**" {
398- item = regexp.MustCompile(`//+`).ReplaceAllString(item, "/")404+ item = doubleSlashRe.ReplaceAllString(item, "/")
399 if !strings.HasSuffix(item, "/") && !p.implicit {405 if !strings.HasSuffix(item, "/") && !p.implicit {
400 item += "/"406 item += "/"
401 }407 }
@@ -429,7 +435,7 @@ func (p *globPattern) partialMatch(item string) bool {
429 }435 }
430 436
431 func trimTrailingSep(s string) string {437 func trimTrailingSep(s string) string {
432- s = regexp.MustCompile(`//+`).ReplaceAllString(s, "/")438+ s = doubleSlashRe.ReplaceAllString(s, "/")
433 if s != "/" && strings.HasSuffix(s, "/") {439 if s != "/" && strings.HasSuffix(s, "/") {
434 return s[:len(s)-1]440 return s[:len(s)-1]
435 }441 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close