From b6ca8abcfdb0a707415769ff9c4bf559e0a21a80 Mon Sep 17 00:00:00 2001 From: Shiny Nematoda Date: Tue, 14 May 2024 15:41:03 +0000 Subject: [PATCH] [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 Co-authored-by: Shiny Nematoda Co-committed-by: Shiny Nematoda --- modules/git/grep.go | 12 ++++++--- modules/git/grep_test.go | 30 ++++++++++++++++++++++ release-notes/8.0.0/feat/3654.md | 1 + routers/web/repo/search.go | 6 ++++- routers/web/repo/view.go | 1 + routers/web/web.go | 14 +++++++--- templates/repo/home.tmpl | 17 +++--------- tests/integration/repo_search_test.go | 9 +++++++ tests/integration/repo_test.go | 37 +++++++++++++++++++++++---- 9 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 release-notes/8.0.0/feat/3654.md diff --git a/modules/git/grep.go b/modules/git/grep.go index 1de739107a..6ca8456cdb 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -29,6 +29,7 @@ type GrepOptions struct { MaxResultLimit int ContextLineNumber int IsFuzzy bool + PathSpec []setting.Glob } 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 { cmd.AddOptionValues("-e", strings.TrimLeft(search, "-")) } + // pathspec - files := make([]string, 0, len(setting.Indexer.IncludePatterns)+len(setting.Indexer.ExcludePatterns)) - for _, expr := range setting.Indexer.IncludePatterns { - files = append(files, expr.Pattern()) + files := make([]string, 0, + len(setting.Indexer.IncludePatterns)+ + 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 { files = append(files, ":^"+expr.Pattern()) } cmd.AddDynamicArguments(cmp.Or(opts.RefName, "HEAD")).AddDashesAndList(files...) + opts.MaxResultLimit = cmp.Or(opts.MaxResultLimit, 50) stderr := bytes.Buffer{} err = cmd.Run(&RunOpts{ diff --git a/modules/git/grep_test.go b/modules/git/grep_test.go index a321b11452..15dc9e9d50 100644 --- a/modules/git/grep_test.go +++ b/modules/git/grep_test.go @@ -76,3 +76,33 @@ func TestGrepLongFiles(t *testing.T) { assert.Len(t, res, 1) 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") +} diff --git a/release-notes/8.0.0/feat/3654.md b/release-notes/8.0.0/feat/3654.md new file mode 100644 index 0000000000..7545599e51 --- /dev/null +++ b/release-notes/8.0.0/feat/3654.md @@ -0,0 +1 @@ +Code Search for non-default branches and tags when repository indexer is disabled diff --git a/routers/web/repo/search.go b/routers/web/repo/search.go index 02df17a7e6..be03c7bded 100644 --- a/routers/web/repo/search.go +++ b/routers/web/repo/search.go @@ -64,7 +64,11 @@ func Search(ctx *context.Context) { ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx) } } 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 { ctx.ServerError("GrepSearch", err) return diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 2644b27229..5b0f4940d1 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -1140,6 +1140,7 @@ PostRecentBranchCheck: ctx.Data["TreeLink"] = treeLink ctx.Data["TreeNames"] = treeNames ctx.Data["BranchLink"] = branchLink + ctx.Data["CodeIndexerDisabled"] = !setting.Indexer.RepoIndexerEnabled ctx.HTML(http.StatusOK, tplRepoHome) } diff --git a/routers/web/web.go b/routers/web/web.go index 48f9102749..77857d32be 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1570,11 +1570,17 @@ func registerRoutes(m *web.Route) { m.Group("/{username}/{reponame}", func() { if !setting.Repository.DisableStars { - m.Get("/stars", repo.Stars) + m.Get("/stars", context.RepoRef(), repo.Stars) } - m.Get("/watchers", repo.Watchers) - m.Get("/search", reqRepoCodeReader, repo.Search) - }, ignSignIn, context.RepoAssignment, context.RepoRef(), context.UnitTypes()) + m.Get("/watchers", context.RepoRef(), repo.Watchers) + m.Group("/search", func() { + 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("/{reponame}", func() { diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 235a7e4503..aa52e91a49 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -8,10 +8,10 @@
{{$description := .Repository.DescriptionHTML $.Context}} - {{if $description}}{{$description | RenderCodeBlock}}{{else if .IsRepositoryAdmin}}{{ctx.Locale.Tr "repo.no_desc"}}{{end}} - {{.Repository.Website}} + {{if $description}}{{$description | RenderCodeBlock}}{{else}}{{ctx.Locale.Tr "repo.no_desc"}}{{end}} + {{if .Repository.Website}}{{.Repository.Website}}{{end}}
-
+
{{template "shared/search/button"}} @@ -106,16 +106,7 @@ {{ctx.Locale.Tr "repo.use_template"}} {{end}} - {{if $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) */}} - -
- - {{template "shared/search/button"}} -
- - {{else}} + {{if (not $isHomepage)}} {{StringUtils.EllipsisString .Repository.Name 30}} {{- range $i, $v := .TreeNames -}} diff --git a/tests/integration/repo_search_test.go b/tests/integration/repo_search_test.go index e058851071..fdfd4cebfb 100644 --- a/tests/integration/repo_search_test.go +++ b/tests/integration/repo_search_test.go @@ -12,6 +12,7 @@ import ( code_indexer "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/routers" "code.gitea.io/gitea/tests" "github.com/PuerkitoBio/goquery" @@ -38,6 +39,7 @@ func TestSearchRepoNoIndexer(t *testing.T) { func testSearchRepo(t *testing.T, indexer bool) { defer tests.PrepareTestEnv(t)() defer test.MockVariableValue(&setting.Indexer.RepoIndexerEnabled, indexer)() + defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1") 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) + 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.ExcludePatterns, setting.IndexerGlobFromString("**/y/**"))() diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 5cf9816d22..3f4cad6bcc 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -190,10 +190,7 @@ func TestViewRepoWithSymlinks(t *testing.T) { // TestViewAsRepoAdmin tests PR #2167 func TestViewAsRepoAdmin(t *testing.T) { - for user, expectedNoDescription := range map[string]bool{ - "user2": true, - "user4": false, - } { + for _, user := range []string{"user2", "user4"} { defer tests.PrepareTestEnv(t)() session := loginUser(t, user) @@ -206,7 +203,7 @@ func TestViewAsRepoAdmin(t *testing.T) { repoTopics := htmlDoc.doc.Find("#repo-topics").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, repoSummary.HasClass("repository-menu")) } @@ -995,3 +992,33 @@ func TestViewRepoOpenWith(t *testing.T) { 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) + }) +}