Improve grep search (#30843)

Reduce the context line number to 1, make "git grep" search respect the
include/exclude patter, and fix #30785
pull/30812/head^2
wxiaoguang 7 months ago committed by GitHub
parent 9f0ef3621a
commit 0f3e717a1a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      modules/git/grep.go
  2. 20
      modules/git/grep_test.go
  3. 32
      modules/setting/glob.go
  4. 12
      modules/setting/indexer.go
  5. 18
      routers/web/repo/search.go
  6. 19
      routers/web/repo/search_test.go

@ -29,6 +29,7 @@ type GrepOptions struct {
ContextLineNumber int ContextLineNumber int
IsFuzzy bool IsFuzzy bool
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
PathspecList []string
} }
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) { func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
@ -62,6 +63,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-")) cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
} }
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD")) cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
cmd.AddDashesAndList(opts.PathspecList...)
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50) opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
stderr := bytes.Buffer{} stderr := bytes.Buffer{}
err = cmd.Run(&RunOpts{ err = cmd.Run(&RunOpts{

@ -31,6 +31,26 @@ func TestGrepSearch(t *testing.T) {
}, },
}, res) }, res)
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob)java-hello/*"}})
assert.NoError(t, err)
assert.Equal(t, []*GrepResult{
{
Filename: "java-hello/main.java",
LineNumbers: []int{3},
LineCodes: []string{" public static void main(String[] args)"},
},
}, res)
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}})
assert.NoError(t, err)
assert.Equal(t, []*GrepResult{
{
Filename: "main.vendor.java",
LineNumbers: []int{3},
LineCodes: []string{" public static void main(String[] args)"},
},
}, res)
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1}) res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, []*GrepResult{ assert.Equal(t, []*GrepResult{

@ -0,0 +1,32 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import "github.com/gobwas/glob"
type GlobMatcher struct {
compiledGlob glob.Glob
patternString string
}
var _ glob.Glob = (*GlobMatcher)(nil)
func (g *GlobMatcher) Match(s string) bool {
return g.compiledGlob.Match(s)
}
func (g *GlobMatcher) PatternString() string {
return g.patternString
}
func GlobMatcherCompile(pattern string, separators ...rune) (*GlobMatcher, error) {
g, err := glob.Compile(pattern, separators...)
if err != nil {
return nil, err
}
return &GlobMatcher{
compiledGlob: g,
patternString: pattern,
}, nil
}

@ -10,8 +10,6 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"github.com/gobwas/glob"
) )
// Indexer settings // Indexer settings
@ -30,8 +28,8 @@ var Indexer = struct {
RepoConnStr string RepoConnStr string
RepoIndexerName string RepoIndexerName string
MaxIndexerFileSize int64 MaxIndexerFileSize int64
IncludePatterns []glob.Glob IncludePatterns []*GlobMatcher
ExcludePatterns []glob.Glob ExcludePatterns []*GlobMatcher
ExcludeVendored bool ExcludeVendored bool
}{ }{
IssueType: "bleve", IssueType: "bleve",
@ -93,12 +91,12 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
} }
// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing // IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing
func IndexerGlobFromString(globstr string) []glob.Glob { func IndexerGlobFromString(globstr string) []*GlobMatcher {
extarr := make([]glob.Glob, 0, 10) extarr := make([]*GlobMatcher, 0, 10)
for _, expr := range strings.Split(strings.ToLower(globstr), ",") { for _, expr := range strings.Split(strings.ToLower(globstr), ",") {
expr = strings.TrimSpace(expr) expr = strings.TrimSpace(expr)
if expr != "" { if expr != "" {
if g, err := glob.Compile(expr, '.', '/'); err != nil { if g, err := GlobMatcherCompile(expr, '.', '/'); err != nil {
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err) log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
} else { } else {
extarr = append(extarr, g) extarr = append(extarr, g)

@ -17,6 +17,16 @@ import (
const tplSearch base.TplName = "repo/search" const tplSearch base.TplName = "repo/search"
func indexSettingToGitGrepPathspecList() (list []string) {
for _, expr := range setting.Indexer.IncludePatterns {
list = append(list, ":(glob)"+expr.PatternString())
}
for _, expr := range setting.Indexer.ExcludePatterns {
list = append(list, ":(glob,exclude)"+expr.PatternString())
}
return list
}
// Search render repository search page // Search render repository search page
func Search(ctx *context.Context) { func Search(ctx *context.Context) {
language := ctx.FormTrim("l") language := ctx.FormTrim("l")
@ -65,8 +75,14 @@ func Search(ctx *context.Context) {
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx) ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
} }
} else { } else {
res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{ContextLineNumber: 3, IsFuzzy: isFuzzy}) res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{
ContextLineNumber: 1,
IsFuzzy: isFuzzy,
RefName: git.RefNameFromBranch(ctx.Repo.BranchName).String(), // BranchName should be default branch or the first existing branch
PathspecList: indexSettingToGitGrepPathspecList(),
})
if err != nil { if err != nil {
// TODO: if no branch exists, it reports: exit status 128, fatal: this operation must be run in a work tree.
ctx.ServerError("GrepSearch", err) ctx.ServerError("GrepSearch", err)
return return
} }

@ -0,0 +1,19 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
func TestIndexSettingToGitGrepPathspecList(t *testing.T) {
defer test.MockVariableValue(&setting.Indexer.IncludePatterns, setting.IndexerGlobFromString("a"))()
defer test.MockVariableValue(&setting.Indexer.ExcludePatterns, setting.IndexerGlobFromString("b"))()
assert.Equal(t, []string{":(glob)a", ":(glob,exclude)b"}, indexSettingToGitGrepPathspecList())
}
Loading…
Cancel
Save