[FEAT] support searching non default branches/tags when using git-grep (#3654)
resolves https://codeberg.org/forgejo/forgejo/pulls/3639#issuecomment-1806676 and https://codeberg.org/forgejo/forgejo/pulls/3513#issuecomment-1794990 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3654 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Shiny Nematoda <snematoda.751k2@aleeas.com> Co-committed-by: Shiny Nematoda <snematoda.751k2@aleeas.com>
This commit is contained in:
parent
9b4452fd7b
commit
b6ca8abcfd
9 changed files with 101 additions and 26 deletions
|
@ -29,6 +29,7 @@ type GrepOptions struct {
|
||||||
MaxResultLimit int
|
MaxResultLimit int
|
||||||
ContextLineNumber int
|
ContextLineNumber int
|
||||||
IsFuzzy bool
|
IsFuzzy bool
|
||||||
|
PathSpec []setting.Glob
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -61,15 +62,20 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
|
||||||
} else {
|
} else {
|
||||||
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// pathspec
|
// pathspec
|
||||||
files := make([]string, 0, len(setting.Indexer.IncludePatterns)+len(setting.Indexer.ExcludePatterns))
|
files := make([]string, 0,
|
||||||
for _, expr := range setting.Indexer.IncludePatterns {
|
len(setting.Indexer.IncludePatterns)+
|
||||||
files = append(files, expr.Pattern())
|
len(setting.Indexer.ExcludePatterns)+
|
||||||
|
len(opts.PathSpec))
|
||||||
|
for _, expr := range append(setting.Indexer.IncludePatterns, opts.PathSpec...) {
|
||||||
|
files = append(files, ":"+expr.Pattern())
|
||||||
}
|
}
|
||||||
for _, expr := range setting.Indexer.ExcludePatterns {
|
for _, expr := range setting.Indexer.ExcludePatterns {
|
||||||
files = append(files, ":^"+expr.Pattern())
|
files = append(files, ":^"+expr.Pattern())
|
||||||
}
|
}
|
||||||
cmd.AddDynamicArguments(cmp.Or(opts.RefName, "HEAD")).AddDashesAndList(files...)
|
cmd.AddDynamicArguments(cmp.Or(opts.RefName, "HEAD")).AddDashesAndList(files...)
|
||||||
|
|
||||||
opts.MaxResultLimit = cmp.Or(opts.MaxResultLimit, 50)
|
opts.MaxResultLimit = cmp.Or(opts.MaxResultLimit, 50)
|
||||||
stderr := bytes.Buffer{}
|
stderr := bytes.Buffer{}
|
||||||
err = cmd.Run(&RunOpts{
|
err = cmd.Run(&RunOpts{
|
||||||
|
|
|
@ -76,3 +76,33 @@ func TestGrepLongFiles(t *testing.T) {
|
||||||
assert.Len(t, res, 1)
|
assert.Len(t, res, 1)
|
||||||
assert.Len(t, res[0].LineCodes[0], 65*1024)
|
assert.Len(t, res[0].LineCodes[0], 65*1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGrepRefs(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
err := InitRepository(DefaultContext, tmpDir, false, Sha1ObjectFormat.Name())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
gitRepo, err := openRepositoryWithDefaultContext(tmpDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
assert.NoError(t, os.WriteFile(path.Join(tmpDir, "README.md"), []byte{'A'}, 0o666))
|
||||||
|
assert.NoError(t, AddChanges(tmpDir, true))
|
||||||
|
|
||||||
|
err = CommitChanges(tmpDir, CommitChangesOptions{Message: "add A"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, gitRepo.CreateTag("v1", "HEAD"))
|
||||||
|
|
||||||
|
assert.NoError(t, os.WriteFile(path.Join(tmpDir, "README.md"), []byte{'A', 'B', 'C', 'D'}, 0o666))
|
||||||
|
assert.NoError(t, AddChanges(tmpDir, true))
|
||||||
|
|
||||||
|
err = CommitChanges(tmpDir, CommitChangesOptions{Message: "add BCD"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
res, err := GrepSearch(context.Background(), gitRepo, "a", GrepOptions{RefName: "v1"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, res, 1)
|
||||||
|
assert.Equal(t, res[0].LineCodes[0], "A")
|
||||||
|
}
|
||||||
|
|
1
release-notes/8.0.0/feat/3654.md
Normal file
1
release-notes/8.0.0/feat/3654.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Code Search for non-default branches and tags when repository indexer is disabled
|
|
@ -64,7 +64,11 @@ 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: ctx.Repo.RefName,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GrepSearch", err)
|
ctx.ServerError("GrepSearch", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1140,6 +1140,7 @@ PostRecentBranchCheck:
|
||||||
ctx.Data["TreeLink"] = treeLink
|
ctx.Data["TreeLink"] = treeLink
|
||||||
ctx.Data["TreeNames"] = treeNames
|
ctx.Data["TreeNames"] = treeNames
|
||||||
ctx.Data["BranchLink"] = branchLink
|
ctx.Data["BranchLink"] = branchLink
|
||||||
|
ctx.Data["CodeIndexerDisabled"] = !setting.Indexer.RepoIndexerEnabled
|
||||||
ctx.HTML(http.StatusOK, tplRepoHome)
|
ctx.HTML(http.StatusOK, tplRepoHome)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1570,11 +1570,17 @@ func registerRoutes(m *web.Route) {
|
||||||
|
|
||||||
m.Group("/{username}/{reponame}", func() {
|
m.Group("/{username}/{reponame}", func() {
|
||||||
if !setting.Repository.DisableStars {
|
if !setting.Repository.DisableStars {
|
||||||
m.Get("/stars", repo.Stars)
|
m.Get("/stars", context.RepoRef(), repo.Stars)
|
||||||
}
|
}
|
||||||
m.Get("/watchers", repo.Watchers)
|
m.Get("/watchers", context.RepoRef(), repo.Watchers)
|
||||||
m.Get("/search", reqRepoCodeReader, repo.Search)
|
m.Group("/search", func() {
|
||||||
}, ignSignIn, context.RepoAssignment, context.RepoRef(), context.UnitTypes())
|
m.Get("", context.RepoRef(), repo.Search)
|
||||||
|
if !setting.Indexer.RepoIndexerEnabled {
|
||||||
|
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Search)
|
||||||
|
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Search)
|
||||||
|
}
|
||||||
|
}, reqRepoCodeReader)
|
||||||
|
}, ignSignIn, context.RepoAssignment, context.UnitTypes())
|
||||||
|
|
||||||
m.Group("/{username}", func() {
|
m.Group("/{username}", func() {
|
||||||
m.Group("/{reponame}", func() {
|
m.Group("/{reponame}", func() {
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
<div class="repo-description">
|
<div class="repo-description">
|
||||||
<div id="repo-desc" class="gt-word-break tw-text-16">
|
<div id="repo-desc" class="gt-word-break tw-text-16">
|
||||||
{{$description := .Repository.DescriptionHTML $.Context}}
|
{{$description := .Repository.DescriptionHTML $.Context}}
|
||||||
{{if $description}}<span class="description">{{$description | RenderCodeBlock}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{ctx.Locale.Tr "repo.no_desc"}}</span>{{end}}
|
{{if $description}}<span class="description">{{$description | RenderCodeBlock}}</span>{{else}}<span class="no-description text-italic">{{ctx.Locale.Tr "repo.no_desc"}}</span>{{end}}
|
||||||
<a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
|
{{if .Repository.Website}}<a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<form class="ignore-dirty" action="{{.RepoLink}}/search" method="get">
|
<form class="ignore-dirty" action="{{.RepoLink}}/search/{{if .CodeIndexerDisabled}}{{.BranchNameSubURL}}{{end}}" method="get" data-test-tag="codesearch">
|
||||||
<div class="ui small action input">
|
<div class="ui small action input">
|
||||||
<input name="q" value="{{.Keyword}}" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
<input name="q" value="{{.Keyword}}" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
||||||
{{template "shared/search/button"}}
|
{{template "shared/search/button"}}
|
||||||
|
@ -106,16 +106,7 @@
|
||||||
{{ctx.Locale.Tr "repo.use_template"}}
|
{{ctx.Locale.Tr "repo.use_template"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if $isHomepage}}
|
{{if (not $isHomepage)}}
|
||||||
{{/* only show the "code search" on the repo home page, it only does global search,
|
|
||||||
so do not show it when viewing file or directory to avoid misleading users (it doesn't search in a directory) */}}
|
|
||||||
<form class="ignore-dirty" action="{{.RepoLink}}/search" method="get">
|
|
||||||
<div class="ui small action input">
|
|
||||||
<input name="q" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
|
||||||
{{template "shared/search/button"}}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{{else}}
|
|
||||||
<span class="breadcrumb repo-path tw-ml-1">
|
<span class="breadcrumb repo-path tw-ml-1">
|
||||||
<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
|
<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
|
||||||
{{- range $i, $v := .TreeNames -}}
|
{{- range $i, $v := .TreeNames -}}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
code_indexer "code.gitea.io/gitea/modules/indexer/code"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
"code.gitea.io/gitea/routers"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
@ -38,6 +39,7 @@ func TestSearchRepoNoIndexer(t *testing.T) {
|
||||||
func testSearchRepo(t *testing.T, indexer bool) {
|
func testSearchRepo(t *testing.T, indexer bool) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
defer test.MockVariableValue(&setting.Indexer.RepoIndexerEnabled, indexer)()
|
defer test.MockVariableValue(&setting.Indexer.RepoIndexerEnabled, indexer)()
|
||||||
|
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
|
||||||
|
|
||||||
repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1")
|
repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -48,6 +50,13 @@ func testSearchRepo(t *testing.T, indexer bool) {
|
||||||
|
|
||||||
testSearch(t, "/user2/repo1/search?q=Description&page=1", []string{"README.md"}, indexer)
|
testSearch(t, "/user2/repo1/search?q=Description&page=1", []string{"README.md"}, indexer)
|
||||||
|
|
||||||
|
req := NewRequest(t, "HEAD", "/user2/repo1/search/branch/"+repo.DefaultBranch)
|
||||||
|
if indexer {
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
} else {
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
defer test.MockVariableValue(&setting.Indexer.IncludePatterns, setting.IndexerGlobFromString("**.txt"))()
|
defer test.MockVariableValue(&setting.Indexer.IncludePatterns, setting.IndexerGlobFromString("**.txt"))()
|
||||||
defer test.MockVariableValue(&setting.Indexer.ExcludePatterns, setting.IndexerGlobFromString("**/y/**"))()
|
defer test.MockVariableValue(&setting.Indexer.ExcludePatterns, setting.IndexerGlobFromString("**/y/**"))()
|
||||||
|
|
||||||
|
|
|
@ -190,10 +190,7 @@ func TestViewRepoWithSymlinks(t *testing.T) {
|
||||||
|
|
||||||
// TestViewAsRepoAdmin tests PR #2167
|
// TestViewAsRepoAdmin tests PR #2167
|
||||||
func TestViewAsRepoAdmin(t *testing.T) {
|
func TestViewAsRepoAdmin(t *testing.T) {
|
||||||
for user, expectedNoDescription := range map[string]bool{
|
for _, user := range []string{"user2", "user4"} {
|
||||||
"user2": true,
|
|
||||||
"user4": false,
|
|
||||||
} {
|
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
session := loginUser(t, user)
|
session := loginUser(t, user)
|
||||||
|
@ -206,7 +203,7 @@ func TestViewAsRepoAdmin(t *testing.T) {
|
||||||
repoTopics := htmlDoc.doc.Find("#repo-topics").Children()
|
repoTopics := htmlDoc.doc.Find("#repo-topics").Children()
|
||||||
repoSummary := htmlDoc.doc.Find(".repository-summary").Children()
|
repoSummary := htmlDoc.doc.Find(".repository-summary").Children()
|
||||||
|
|
||||||
assert.Equal(t, expectedNoDescription, noDescription.HasClass("no-description"))
|
assert.True(t, noDescription.HasClass("no-description"))
|
||||||
assert.True(t, repoTopics.HasClass("repo-topic"))
|
assert.True(t, repoTopics.HasClass("repo-topic"))
|
||||||
assert.True(t, repoSummary.HasClass("repository-menu"))
|
assert.True(t, repoSummary.HasClass("repository-menu"))
|
||||||
}
|
}
|
||||||
|
@ -995,3 +992,33 @@ func TestViewRepoOpenWith(t *testing.T) {
|
||||||
testOpenWith([]string{"test://"})
|
testOpenWith([]string{"test://"})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRepoCodeSearchForm(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
testSearchForm := func(t *testing.T, indexer bool) {
|
||||||
|
defer test.MockVariableValue(&setting.Indexer.RepoIndexerEnabled, indexer)()
|
||||||
|
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master")
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
action, exists := htmlDoc.doc.Find("form[data-test-tag=codesearch]").Attr("action")
|
||||||
|
assert.True(t, exists)
|
||||||
|
|
||||||
|
branchSubURL := "/branch/master"
|
||||||
|
|
||||||
|
if indexer {
|
||||||
|
assert.NotContains(t, action, branchSubURL)
|
||||||
|
} else {
|
||||||
|
assert.Contains(t, action, branchSubURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("indexer disabled", func(t *testing.T) {
|
||||||
|
testSearchForm(t, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("indexer enabled", func(t *testing.T) {
|
||||||
|
testSearchForm(t, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue