diff --git a/modules/git/grep.go b/modules/git/grep.go index 6ca8456cdb..7cd1a96da6 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -27,6 +27,7 @@ type GrepResult struct { type GrepOptions struct { RefName string MaxResultLimit int + MatchesPerFile int ContextLineNumber int IsFuzzy bool PathSpec []setting.Glob @@ -54,6 +55,9 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO var results []*GrepResult cmd := NewCommand(ctx, "grep", "--null", "--break", "--heading", "--fixed-strings", "--line-number", "--ignore-case", "--full-name") cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber)) + if opts.MatchesPerFile > 0 { + cmd.AddOptionValues("--max-count", fmt.Sprint(opts.MatchesPerFile)) + } if opts.IsFuzzy { words := strings.Fields(search) for _, word := range words { diff --git a/modules/git/grep_test.go b/modules/git/grep_test.go index 15dc9e9d50..d2ed7300c1 100644 --- a/modules/git/grep_test.go +++ b/modules/git/grep_test.go @@ -44,6 +44,31 @@ func TestGrepSearch(t *testing.T) { }, }, res) + res, err = GrepSearch(context.Background(), repo, "world", GrepOptions{MatchesPerFile: 1}) + assert.NoError(t, err) + assert.Equal(t, []*GrepResult{ + { + Filename: "i-am-a-python.p", + LineNumbers: []int{1}, + LineCodes: []string{"## This is a simple file to do a hello world"}, + }, + { + Filename: "java-hello/main.java", + LineNumbers: []int{1}, + LineCodes: []string{"public class HelloWorld"}, + }, + { + Filename: "main.vendor.java", + LineNumbers: []int{1}, + LineCodes: []string{"public class HelloWorld"}, + }, + { + Filename: "python-hello/hello.py", + LineNumbers: []int{1}, + LineCodes: []string{"## This is a simple file to do a hello world"}, + }, + }, res) + res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{}) assert.NoError(t, err) assert.Len(t, res, 0) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f9df8ac46c..294a3765c8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2016,6 +2016,8 @@ wiki.pages = Pages wiki.last_updated = Last updated %s wiki.page_name_desc = Enter a name for this Wiki page. Some special names are: "Home", "_Sidebar" and "_Footer". wiki.original_git_entry_tooltip = View original Git file instead of using friendly link. +wiki.search = Search wiki +wiki.no_search_results = No results activity = Activity activity.navbar.pulse = Pulse diff --git a/release-notes/8.0.0/feat/3847.md b/release-notes/8.0.0/feat/3847.md new file mode 100644 index 0000000000..3ff9e872d7 --- /dev/null +++ b/release-notes/8.0.0/feat/3847.md @@ -0,0 +1,3 @@ +Basic wiki content search using git-grep + - The search results include the first ten matched files + - Only the first three matches per file are displayed diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index f0743cc89c..4911fb6452 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -40,6 +40,7 @@ const ( tplWikiRevision base.TplName = "repo/wiki/revision" tplWikiNew base.TplName = "repo/wiki/new" tplWikiPages base.TplName = "repo/wiki/pages" + tplWikiSearch base.TplName = "repo/wiki/search" ) // MustEnableWiki check if wiki is enabled, if external then redirect @@ -795,3 +796,20 @@ func DeleteWikiPagePost(ctx *context.Context) { ctx.JSONRedirect(ctx.Repo.RepoLink + "/wiki/") } + +func WikiSearchContent(ctx *context.Context) { + keyword := ctx.FormTrim("q") + if keyword == "" { + ctx.HTML(http.StatusOK, tplWikiSearch) + return + } + + res, err := wiki_service.SearchWikiContents(ctx, ctx.Repo.Repository, keyword) + if err != nil { + ctx.ServerError("SearchWikiContents", err) + return + } + + ctx.Data["Results"] = res + ctx.HTML(http.StatusOK, tplWikiSearch) +} diff --git a/routers/web/web.go b/routers/web/web.go index 77857d32be..0ab25fd7e3 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1417,6 +1417,7 @@ func registerRoutes(m *web.Route) { }) m.Group("/wiki", func() { + m.Get("/search", repo.WikiSearchContent) m.Get("/raw/*", repo.WikiRaw) }, repo.MustEnableWiki) diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index ec5d0fc9e2..24779d41e0 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -407,3 +407,19 @@ func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error { system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath()) return nil } + +func SearchWikiContents(ctx context.Context, repo *repo_model.Repository, keyword string) ([]*git.GrepResult, error) { + gitRepo, err := git.OpenRepository(ctx, repo.WikiPath()) + if err != nil { + return nil, err + } + defer gitRepo.Close() + + return git.GrepSearch(ctx, gitRepo, keyword, git.GrepOptions{ + ContextLineNumber: 0, + IsFuzzy: true, + RefName: repo.GetWikiBranchName(), + MaxResultLimit: 10, + MatchesPerFile: 3, + }) +} diff --git a/templates/repo/wiki/search.tmpl b/templates/repo/wiki/search.tmpl new file mode 100644 index 0000000000..88b12b08b9 --- /dev/null +++ b/templates/repo/wiki/search.tmpl @@ -0,0 +1,12 @@ +{{if .Results}} + {{range .Results}} + + {{.Filename}} + {{range .LineCodes}} +

{{.}}

+ {{end}} +
+ {{end}} +{{else}} +
{{ctx.Locale.Tr "repo.wiki.no_search_results"}}
+{{end}} diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index feb7192bb1..19b6968796 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -32,6 +32,15 @@ {{template "repo/clone_buttons" .}} {{template "repo/clone_script" .}} +
diff --git a/tests/integration/repo_wiki_test.go b/tests/integration/repo_wiki_test.go index 171191e165..316c04503a 100644 --- a/tests/integration/repo_wiki_test.go +++ b/tests/integration/repo_wiki_test.go @@ -15,9 +15,26 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/tests" + "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" ) +func TestWikiSearchContent(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/user2/repo1/wiki/search?q=This") + resp := MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + res := doc.Find(".item > b").Map(func(_ int, el *goquery.Selection) string { + return el.Text() + }) + assert.Equal(t, []string{ + "Home.md", + "Page-With-Spaced-Name.md", + "Unescaped File.md", + }, res) +} + func TestWikiBranchNormalize(t *testing.T) { defer tests.PrepareTestEnv(t)()