mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-24 21:53:39 +09:00
perf(references): scan only the keyword window before a reference (#39396)
Fixes https://github.com/go-gitea/gitea/issues/39395 `findActionKeywords` ran the close and reopen keyword patterns over all content before each reference, making `FindAllIssueReferences` quadratic on comments and commit messages. The patterns are anchored at the reference, so only the last few bytes can match. Scan only that window, sized from the longest keyword with room for `(?i)` matching wider runes like `ſ` for `s`. `"#1 "` repeated 4000 times: 2.04 s before, 16 ms after. --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
co-authored by
silverwind
wxiaoguang
parent
2177969aba
commit
8b46a956d8
@@ -7,9 +7,11 @@ import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/markup/mdstripper"
|
||||
@@ -45,6 +47,7 @@ var (
|
||||
timeLogPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@([0-9]+([\.,][0-9]+)?(w|d|m|h))+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
|
||||
|
||||
issueCloseKeywordsPat, issueReopenKeywordsPat *regexp.Regexp
|
||||
issueKeywordWindow int
|
||||
issueKeywordsOnce sync.Once
|
||||
|
||||
giteaHostInit sync.Once
|
||||
@@ -167,6 +170,10 @@ func newKeywords() {
|
||||
func doNewKeywords(closeKeywords, reopenKeywords []string) {
|
||||
issueCloseKeywordsPat = makeKeywordsPat(closeKeywords)
|
||||
issueReopenKeywordsPat = makeKeywordsPat(reopenKeywords)
|
||||
issueKeywordWindow = 0
|
||||
for _, word := range slices.Concat(closeKeywords, reopenKeywords) {
|
||||
issueKeywordWindow = max(issueKeywordWindow, utf8.RuneCountInString(word)*utf8.UTFMax+3) // delimiter, keyword with (?i)-folded runes like ſ for s, ": "
|
||||
}
|
||||
}
|
||||
|
||||
// getGiteaHostName returns a normalized string with the local host name, with no scheme or port information
|
||||
@@ -599,17 +606,16 @@ func getCrossReference(content []byte, start, end int, fromLink, prOnly bool) *r
|
||||
|
||||
func findActionKeywords(content []byte, start int) (XRefAction, *RefSpan) {
|
||||
newKeywords()
|
||||
var m []int
|
||||
windowStart := max(start-issueKeywordWindow, 0)
|
||||
prefix := content[windowStart:start]
|
||||
if issueCloseKeywordsPat != nil {
|
||||
m = issueCloseKeywordsPat.FindSubmatchIndex(content[:start])
|
||||
if m != nil {
|
||||
return XRefActionCloses, &RefSpan{Start: m[2], End: m[3]}
|
||||
if m := issueCloseKeywordsPat.FindSubmatchIndex(prefix); m != nil {
|
||||
return XRefActionCloses, &RefSpan{Start: windowStart + m[2], End: windowStart + m[3]}
|
||||
}
|
||||
}
|
||||
if issueReopenKeywordsPat != nil {
|
||||
m = issueReopenKeywordsPat.FindSubmatchIndex(content[:start])
|
||||
if m != nil {
|
||||
return XRefActionReopens, &RefSpan{Start: m[2], End: m[3]}
|
||||
if m := issueReopenKeywordsPat.FindSubmatchIndex(prefix); m != nil {
|
||||
return XRefActionReopens, &RefSpan{Start: windowStart + m[2], End: windowStart + m[3]}
|
||||
}
|
||||
}
|
||||
return XRefActionNone, nil
|
||||
|
||||
@@ -162,6 +162,13 @@ func TestFindAllIssueReferences(t *testing.T) {
|
||||
{1235, "", "", "1235", false, XRefActionNone, &RefSpan{Start: 8, End: 13}, nil, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
"After a long investigation into the root cause, this resolves #1, unlike the still unresolved #2",
|
||||
[]testResult{
|
||||
{1, "", "", "1", false, XRefActionCloses, &RefSpan{Start: 62, End: 64}, &RefSpan{Start: 53, End: 61}, ""},
|
||||
{2, "", "", "2", false, XRefActionNone, &RefSpan{Start: 94, End: 96}, nil, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
"For [!123] yes",
|
||||
[]testResult{
|
||||
|
||||
Reference in New Issue
Block a user