Skip to content

Commit 3d45dc9

Browse files
committed
Merge branch 'main' into lunny/actions_log_api
2 parents 14b6ce7 + 403775e commit 3d45dc9

File tree

47 files changed

+413
-235
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+413
-235
lines changed

modules/git/grep.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,19 @@ type GrepResult struct {
2323
LineCodes []string
2424
}
2525

26+
type GrepModeType string
27+
28+
const (
29+
GrepModeExact GrepModeType = "exact"
30+
GrepModeWords GrepModeType = "words"
31+
GrepModeRegexp GrepModeType = "regexp"
32+
)
33+
2634
type GrepOptions struct {
2735
RefName string
2836
MaxResultLimit int
2937
ContextLineNumber int
30-
IsFuzzy bool
38+
GrepMode GrepModeType
3139
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
3240
PathspecList []string
3341
}
@@ -52,15 +60,23 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
5260
2^@repo: go-gitea/gitea
5361
*/
5462
var results []*GrepResult
55-
cmd := NewCommand("grep", "--null", "--break", "--heading", "--fixed-strings", "--line-number", "--ignore-case", "--full-name")
63+
cmd := NewCommand("grep", "--null", "--break", "--heading", "--line-number", "--full-name")
5664
cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber))
57-
if opts.IsFuzzy {
65+
if opts.GrepMode == GrepModeExact {
66+
cmd.AddArguments("--fixed-strings")
67+
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
68+
} else if opts.GrepMode == GrepModeRegexp {
69+
cmd.AddArguments("--perl-regexp")
70+
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
71+
} else /* words */ {
5872
words := strings.Fields(search)
59-
for _, word := range words {
73+
cmd.AddArguments("--fixed-strings", "--ignore-case")
74+
for i, word := range words {
6075
cmd.AddOptionValues("-e", strings.TrimLeft(word, "-"))
76+
if i < len(words)-1 {
77+
cmd.AddOptionValues("--and")
78+
}
6179
}
62-
} else {
63-
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
6480
}
6581
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
6682
cmd.AddDashesAndList(opts.PathspecList...)

modules/httpcache/httpcache.go

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,60 @@
44
package httpcache
55

66
import (
7-
"io"
7+
"fmt"
88
"net/http"
99
"strconv"
1010
"strings"
1111
"time"
1212

1313
"code.gitea.io/gitea/modules/setting"
14+
"code.gitea.io/gitea/modules/util"
1415
)
1516

17+
type CacheControlOptions struct {
18+
IsPublic bool
19+
MaxAge time.Duration
20+
NoTransform bool
21+
}
22+
1623
// SetCacheControlInHeader sets suitable cache-control headers in the response
17-
func SetCacheControlInHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
18-
directives := make([]string, 0, 2+len(additionalDirectives))
24+
func SetCacheControlInHeader(h http.Header, opts *CacheControlOptions) {
25+
directives := make([]string, 0, 4)
1926

2027
// "max-age=0 + must-revalidate" (aka "no-cache") is preferred instead of "no-store"
2128
// because browsers may restore some input fields after navigate-back / reload a page.
29+
publicPrivate := util.Iif(opts.IsPublic, "public", "private")
2230
if setting.IsProd {
23-
if maxAge == 0 {
31+
if opts.MaxAge == 0 {
2432
directives = append(directives, "max-age=0", "private", "must-revalidate")
2533
} else {
26-
directives = append(directives, "private", "max-age="+strconv.Itoa(int(maxAge.Seconds())))
34+
directives = append(directives, publicPrivate, "max-age="+strconv.Itoa(int(opts.MaxAge.Seconds())))
2735
}
2836
} else {
29-
directives = append(directives, "max-age=0", "private", "must-revalidate")
37+
// use dev-related controls, and remind users they are using non-prod setting.
38+
directives = append(directives, "max-age=0", publicPrivate, "must-revalidate")
39+
h.Set("X-Gitea-Debug", fmt.Sprintf("RUN_MODE=%v, MaxAge=%s", setting.RunMode, opts.MaxAge))
40+
}
3041

31-
// to remind users they are using non-prod setting.
32-
h.Set("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
42+
if opts.NoTransform {
43+
directives = append(directives, "no-transform")
3344
}
45+
h.Set("Cache-Control", strings.Join(directives, ", "))
46+
}
3447

35-
h.Set("Cache-Control", strings.Join(append(directives, additionalDirectives...), ", "))
48+
func CacheControlForPublicStatic() *CacheControlOptions {
49+
return &CacheControlOptions{
50+
IsPublic: true,
51+
MaxAge: setting.StaticCacheTime,
52+
NoTransform: true,
53+
}
3654
}
3755

38-
func ServeContentWithCacheControl(w http.ResponseWriter, req *http.Request, name string, modTime time.Time, content io.ReadSeeker) {
39-
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
40-
http.ServeContent(w, req, name, modTime, content)
56+
func CacheControlForPrivateStatic() *CacheControlOptions {
57+
return &CacheControlOptions{
58+
MaxAge: setting.StaticCacheTime,
59+
NoTransform: true,
60+
}
4161
}
4262

4363
// HandleGenericETagCache handles ETag-based caching for a HTTP request.
@@ -50,7 +70,8 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin
5070
return true
5171
}
5272
}
53-
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
73+
// not sure whether it is a public content, so just use "private" (old behavior)
74+
SetCacheControlInHeader(w.Header(), CacheControlForPrivateStatic())
5475
return false
5576
}
5677

@@ -95,6 +116,8 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s
95116
}
96117
}
97118
}
98-
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
119+
120+
// not sure whether it is a public content, so just use "private" (old behavior)
121+
SetCacheControlInHeader(w.Header(), CacheControlForPrivateStatic())
99122
return false
100123
}

modules/httplib/serve.go

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type ServeHeaderOptions struct {
3333
ContentLength *int64
3434
Disposition string // defaults to "attachment"
3535
Filename string
36+
CacheIsPublic bool
3637
CacheDuration time.Duration // defaults to 5 minutes
3738
LastModified time.Time
3839
}
@@ -72,11 +73,11 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
7273
header.Set("Access-Control-Expose-Headers", "Content-Disposition")
7374
}
7475

75-
duration := opts.CacheDuration
76-
if duration == 0 {
77-
duration = 5 * time.Minute
78-
}
79-
httpcache.SetCacheControlInHeader(header, duration)
76+
httpcache.SetCacheControlInHeader(header, &httpcache.CacheControlOptions{
77+
IsPublic: opts.CacheIsPublic,
78+
MaxAge: opts.CacheDuration,
79+
NoTransform: true,
80+
})
8081

8182
if !opts.LastModified.IsZero() {
8283
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
@@ -85,19 +86,15 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
8586
}
8687

8788
// ServeData download file from io.Reader
88-
func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, filePath string, mineBuf []byte) {
89+
func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, mineBuf []byte, opts *ServeHeaderOptions) {
8990
// do not set "Content-Length", because the length could only be set by callers, and it needs to support range requests
90-
opts := &ServeHeaderOptions{
91-
Filename: path.Base(filePath),
92-
}
93-
9491
sniffedType := typesniffer.DetectContentType(mineBuf)
9592

9693
// the "render" parameter came from year 2016: 638dd24c, it doesn't have clear meaning, so I think it could be removed later
9794
isPlain := sniffedType.IsText() || r.FormValue("render") != ""
9895

9996
if setting.MimeTypeMap.Enabled {
100-
fileExtension := strings.ToLower(filepath.Ext(filePath))
97+
fileExtension := strings.ToLower(filepath.Ext(opts.Filename))
10198
opts.ContentType = setting.MimeTypeMap.Map[fileExtension]
10299
}
103100

@@ -114,7 +111,7 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, filePath stri
114111
if isPlain {
115112
charset, err := charsetModule.DetectEncoding(mineBuf)
116113
if err != nil {
117-
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err)
114+
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", opts.Filename, err)
118115
charset = "utf-8"
119116
}
120117
opts.ContentTypeCharset = strings.ToLower(charset)
@@ -142,7 +139,7 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, filePath stri
142139

143140
const mimeDetectionBufferLen = 1024
144141

145-
func ServeContentByReader(r *http.Request, w http.ResponseWriter, filePath string, size int64, reader io.Reader) {
142+
func ServeContentByReader(r *http.Request, w http.ResponseWriter, size int64, reader io.Reader, opts *ServeHeaderOptions) {
146143
buf := make([]byte, mimeDetectionBufferLen)
147144
n, err := util.ReadAtMost(reader, buf)
148145
if err != nil {
@@ -152,7 +149,7 @@ func ServeContentByReader(r *http.Request, w http.ResponseWriter, filePath strin
152149
if n >= 0 {
153150
buf = buf[:n]
154151
}
155-
setServeHeadersByFile(r, w, filePath, buf)
152+
setServeHeadersByFile(r, w, buf, opts)
156153

157154
// reset the reader to the beginning
158155
reader = io.MultiReader(bytes.NewReader(buf), reader)
@@ -215,7 +212,7 @@ func ServeContentByReader(r *http.Request, w http.ResponseWriter, filePath strin
215212
_, _ = io.CopyN(w, reader, partialLength) // just like http.ServeContent, not necessary to handle the error
216213
}
217214

218-
func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, filePath string, modTime *time.Time, reader io.ReadSeeker) {
215+
func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, modTime *time.Time, reader io.ReadSeeker, opts *ServeHeaderOptions) {
219216
buf := make([]byte, mimeDetectionBufferLen)
220217
n, err := util.ReadAtMost(reader, buf)
221218
if err != nil {
@@ -229,9 +226,9 @@ func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, filePath s
229226
if n >= 0 {
230227
buf = buf[:n]
231228
}
232-
setServeHeadersByFile(r, w, filePath, buf)
229+
setServeHeadersByFile(r, w, buf, opts)
233230
if modTime == nil {
234231
modTime = &time.Time{}
235232
}
236-
http.ServeContent(w, r, path.Base(filePath), *modTime, reader)
233+
http.ServeContent(w, r, opts.Filename, *modTime, reader)
237234
}

modules/httplib/serve_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func TestServeContentByReader(t *testing.T) {
2727
}
2828
reader := strings.NewReader(data)
2929
w := httptest.NewRecorder()
30-
ServeContentByReader(r, w, "test", int64(len(data)), reader)
30+
ServeContentByReader(r, w, int64(len(data)), reader, &ServeHeaderOptions{})
3131
assert.Equal(t, expectedStatusCode, w.Code)
3232
if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK {
3333
assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length"))
@@ -76,7 +76,7 @@ func TestServeContentByReadSeeker(t *testing.T) {
7676
defer seekReader.Close()
7777

7878
w := httptest.NewRecorder()
79-
ServeContentByReadSeeker(r, w, "test", nil, seekReader)
79+
ServeContentByReadSeeker(r, w, nil, seekReader, &ServeHeaderOptions{})
8080
assert.Equal(t, expectedStatusCode, w.Code)
8181
if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK {
8282
assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length"))

modules/indexer/code/bleve/bleve.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"code.gitea.io/gitea/modules/charset"
1818
"code.gitea.io/gitea/modules/git"
1919
"code.gitea.io/gitea/modules/gitrepo"
20+
"code.gitea.io/gitea/modules/indexer"
2021
path_filter "code.gitea.io/gitea/modules/indexer/code/bleve/token/path"
2122
"code.gitea.io/gitea/modules/indexer/code/internal"
2223
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
@@ -136,6 +137,10 @@ type Indexer struct {
136137
indexer_internal.Indexer // do not composite inner_bleve.Indexer directly to avoid exposing too much
137138
}
138139

140+
func (b *Indexer) SupportedSearchModes() []indexer.SearchMode {
141+
return indexer.SearchModesExactWords()
142+
}
143+
139144
// NewIndexer creates a new bleve local indexer
140145
func NewIndexer(indexDir string) *Indexer {
141146
inner := inner_bleve.NewIndexer(indexDir, repoIndexerLatestVersion, generateBleveIndexMapping)
@@ -267,19 +272,18 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
267272
pathQuery.FieldVal = "Filename"
268273
pathQuery.SetBoost(10)
269274

270-
keywordAsPhrase, isPhrase := internal.ParseKeywordAsPhrase(opts.Keyword)
271-
if isPhrase {
272-
q := bleve.NewMatchPhraseQuery(keywordAsPhrase)
275+
if opts.SearchMode == indexer.SearchModeExact {
276+
q := bleve.NewMatchPhraseQuery(opts.Keyword)
273277
q.FieldVal = "Content"
274-
if opts.IsKeywordFuzzy {
275-
q.Fuzziness = inner_bleve.GuessFuzzinessByKeyword(keywordAsPhrase)
276-
}
277278
contentQuery = q
278-
} else {
279+
} else /* words */ {
279280
q := bleve.NewMatchQuery(opts.Keyword)
280281
q.FieldVal = "Content"
281-
if opts.IsKeywordFuzzy {
282+
if opts.SearchMode == indexer.SearchModeFuzzy {
283+
// this logic doesn't seem right, it is only used to pass the test-case `Keyword: "dESCRIPTION"`, which doesn't seem to be a real-life use-case.
282284
q.Fuzziness = inner_bleve.GuessFuzzinessByKeyword(opts.Keyword)
285+
} else {
286+
q.Operator = query.MatchQueryOperatorAnd
283287
}
284288
contentQuery = q
285289
}

modules/indexer/code/elasticsearch/elasticsearch.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"code.gitea.io/gitea/modules/charset"
1717
"code.gitea.io/gitea/modules/git"
1818
"code.gitea.io/gitea/modules/gitrepo"
19+
"code.gitea.io/gitea/modules/indexer"
1920
"code.gitea.io/gitea/modules/indexer/code/internal"
2021
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
2122
inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch"
@@ -24,7 +25,6 @@ import (
2425
"code.gitea.io/gitea/modules/setting"
2526
"code.gitea.io/gitea/modules/timeutil"
2627
"code.gitea.io/gitea/modules/typesniffer"
27-
"code.gitea.io/gitea/modules/util"
2828

2929
"github.com/go-enry/go-enry/v2"
3030
"github.com/olivere/elastic/v7"
@@ -46,6 +46,10 @@ type Indexer struct {
4646
indexer_internal.Indexer // do not composite inner_elasticsearch.Indexer directly to avoid exposing too much
4747
}
4848

49+
func (b *Indexer) SupportedSearchModes() []indexer.SearchMode {
50+
return indexer.SearchModesExactWords()
51+
}
52+
4953
// NewIndexer creates a new elasticsearch indexer
5054
func NewIndexer(url, indexerName string) *Indexer {
5155
inner := inner_elasticsearch.NewIndexer(url, indexerName, esRepoIndexerLatestVersion, defaultMapping)
@@ -361,15 +365,10 @@ func extractAggs(searchResult *elastic.SearchResult) []*internal.SearchResultLan
361365
// Search searches for codes and language stats by given conditions.
362366
func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
363367
var contentQuery elastic.Query
364-
keywordAsPhrase, isPhrase := internal.ParseKeywordAsPhrase(opts.Keyword)
365-
if isPhrase {
366-
contentQuery = elastic.NewMatchPhraseQuery("content", keywordAsPhrase)
367-
} else {
368-
// TODO: this is the old logic, but not really using "fuzziness"
369-
// * IsKeywordFuzzy=true: "best_fields"
370-
// * IsKeywordFuzzy=false: "phrase_prefix"
371-
contentQuery = elastic.NewMultiMatchQuery("content", opts.Keyword).
372-
Type(util.Iif(opts.IsKeywordFuzzy, esMultiMatchTypeBestFields, esMultiMatchTypePhrasePrefix))
368+
if opts.SearchMode == indexer.SearchModeExact {
369+
contentQuery = elastic.NewMatchPhraseQuery("content", opts.Keyword)
370+
} else /* words */ {
371+
contentQuery = elastic.NewMultiMatchQuery("content", opts.Keyword).Type(esMultiMatchTypeBestFields).Operator("and")
373372
}
374373
kwQuery := elastic.NewBoolQuery().Should(
375374
contentQuery,

modules/indexer/code/gitgrep/gitgrep.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010

1111
"code.gitea.io/gitea/modules/git"
12+
"code.gitea.io/gitea/modules/indexer"
1213
code_indexer "code.gitea.io/gitea/modules/indexer/code"
1314
"code.gitea.io/gitea/modules/setting"
1415
)
@@ -23,11 +24,16 @@ func indexSettingToGitGrepPathspecList() (list []string) {
2324
return list
2425
}
2526

26-
func PerformSearch(ctx context.Context, page int, repoID int64, gitRepo *git.Repository, ref git.RefName, keyword string, isFuzzy bool) (searchResults []*code_indexer.Result, total int, err error) {
27-
// TODO: it should also respect ParseKeywordAsPhrase and clarify the "fuzzy" behavior
27+
func PerformSearch(ctx context.Context, page int, repoID int64, gitRepo *git.Repository, ref git.RefName, keyword string, searchMode indexer.SearchModeType) (searchResults []*code_indexer.Result, total int, err error) {
28+
grepMode := git.GrepModeWords
29+
if searchMode == indexer.SearchModeExact {
30+
grepMode = git.GrepModeExact
31+
} else if searchMode == indexer.SearchModeRegexp {
32+
grepMode = git.GrepModeRegexp
33+
}
2834
res, err := git.GrepSearch(ctx, gitRepo, keyword, git.GrepOptions{
2935
ContextLineNumber: 1,
30-
IsFuzzy: isFuzzy,
36+
GrepMode: grepMode,
3137
RefName: ref.String(),
3238
PathspecList: indexSettingToGitGrepPathspecList(),
3339
})

modules/indexer/code/indexer.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"code.gitea.io/gitea/models/db"
1515
repo_model "code.gitea.io/gitea/models/repo"
1616
"code.gitea.io/gitea/modules/graceful"
17+
"code.gitea.io/gitea/modules/indexer"
1718
"code.gitea.io/gitea/modules/indexer/code/bleve"
1819
"code.gitea.io/gitea/modules/indexer/code/elasticsearch"
1920
"code.gitea.io/gitea/modules/indexer/code/internal"
@@ -302,3 +303,11 @@ func populateRepoIndexer(ctx context.Context) {
302303
}
303304
log.Info("Done (re)populating the repo indexer with existing repositories")
304305
}
306+
307+
func SupportedSearchModes() []indexer.SearchMode {
308+
gi := globalIndexer.Load()
309+
if gi == nil {
310+
return nil
311+
}
312+
return (*gi).SupportedSearchModes()
313+
}

0 commit comments

Comments
 (0)