Skip to content

Commit 666155e

Browse files
committed
fix
1 parent 2f060c5 commit 666155e

File tree

6 files changed

+223
-54
lines changed

6 files changed

+223
-54
lines changed

modules/git/grep.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package git
5+
6+
import (
7+
"bufio"
8+
"context"
9+
"fmt"
10+
"io"
11+
"os"
12+
"strconv"
13+
"strings"
14+
15+
"code.gitea.io/gitea/modules/util"
16+
)
17+
18+
type GrepResult struct {
19+
Filename string
20+
LineNumbers []int
21+
LineCodes []string
22+
}
23+
24+
type GrepOptions struct {
25+
RefName string
26+
ContextLineNumber int
27+
IsFuzzy bool
28+
}
29+
30+
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
31+
stdoutReader, stdoutWriter, err := os.Pipe()
32+
if err != nil {
33+
return nil, fmt.Errorf("unable to creata os pipe to grep: %w", err)
34+
}
35+
stderrReader, stderrWriter, err := os.Pipe()
36+
if err != nil {
37+
return nil, fmt.Errorf("unable to creata os pipe to grep: %w", err)
38+
}
39+
defer func() {
40+
_ = stdoutReader.Close()
41+
_ = stdoutWriter.Close()
42+
_ = stderrReader.Close()
43+
_ = stderrWriter.Close()
44+
}()
45+
46+
/*
47+
The output is like this ( "^@" means \x00):
48+
49+
HEAD:.air.toml
50+
6^@bin = "gitea"
51+
52+
HEAD:.changelog.yml
53+
2^@repo: go-gitea/gitea
54+
*/
55+
var stderr []byte
56+
var results []*GrepResult
57+
cmd := NewCommand(ctx, "grep", "--null", "--break", "--heading", "--fixed-strings", "--line-number", "--ignore-case", "--full-name")
58+
cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber))
59+
if opts.IsFuzzy {
60+
words := strings.Fields(search)
61+
for _, word := range words {
62+
cmd.AddOptionValues("-e", word)
63+
}
64+
} else {
65+
cmd.AddOptionValues("-e", search)
66+
}
67+
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
68+
err = cmd.Run(&RunOpts{
69+
Dir: repo.Path,
70+
Stdout: stdoutWriter,
71+
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
72+
_ = stdoutWriter.Close()
73+
_ = stderrWriter.Close()
74+
defer stdoutReader.Close()
75+
defer stderrReader.Close()
76+
77+
isInBlock := false
78+
scanner := bufio.NewScanner(stdoutReader)
79+
var res *GrepResult
80+
for scanner.Scan() {
81+
line := scanner.Text()
82+
if !isInBlock {
83+
if _ /* ref */, filename, ok := strings.Cut(line, ":"); ok {
84+
isInBlock = true
85+
res = &GrepResult{Filename: filename}
86+
results = append(results, res)
87+
}
88+
continue
89+
}
90+
if line == "" {
91+
if len(results) >= 50 {
92+
break
93+
}
94+
isInBlock = false
95+
continue
96+
}
97+
if line == "--" {
98+
continue
99+
}
100+
if lineNum, lineCode, ok := strings.Cut(line, "\x00"); ok {
101+
lineNumInt, _ := strconv.Atoi(lineNum)
102+
res.LineNumbers = append(res.LineNumbers, lineNumInt)
103+
res.LineCodes = append(res.LineCodes, lineCode)
104+
}
105+
}
106+
stderr, _ = io.ReadAll(stderrReader)
107+
return scanner.Err()
108+
},
109+
})
110+
if err != nil && len(stderr) != 0 {
111+
return nil, fmt.Errorf("unable to run grep: %w, stderr: %s", err, string(stderr))
112+
}
113+
return results, nil
114+
}

modules/git/grep_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package git
5+
6+
import (
7+
"context"
8+
"path/filepath"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestGrepSearch(t *testing.T) {
15+
repo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "language_stats_repo"))
16+
assert.NoError(t, err)
17+
defer repo.Close()
18+
19+
res, err := GrepSearch(context.Background(), repo, "void", GrepOptions{})
20+
assert.NoError(t, err)
21+
assert.Equal(t, []*GrepResult{
22+
{
23+
Filename: "java-hello/main.java",
24+
LineNumbers: []int{3},
25+
LineCodes: []string{" public static void main(String[] args)"},
26+
},
27+
{
28+
Filename: "main.vendor.java",
29+
LineNumbers: []int{3},
30+
LineCodes: []string{" public static void main(String[] args)"},
31+
},
32+
}, res)
33+
34+
res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{})
35+
assert.NoError(t, err)
36+
assert.Len(t, res, 0)
37+
}

modules/indexer/code/search.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,27 @@ func writeStrings(buf *bytes.Buffer, strs ...string) error {
7070
return nil
7171
}
7272

73+
func HighlightSearchResultCode(filename string, lineNums []int, code string) []ResultLine {
74+
// we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting
75+
hl, _ := highlight.Code(filename, "", code)
76+
highlightedLines := strings.Split(string(hl), "\n")
77+
78+
// The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n`
79+
lines := make([]ResultLine, min(len(highlightedLines), len(lineNums)))
80+
for i := 0; i < len(lines); i++ {
81+
lines[i].Num = lineNums[i]
82+
lines[i].FormattedContent = template.HTML(highlightedLines[i])
83+
}
84+
return lines
85+
}
86+
7387
func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Result, error) {
7488
startLineNum := 1 + strings.Count(result.Content[:startIndex], "\n")
7589

7690
var formattedLinesBuffer bytes.Buffer
7791

7892
contentLines := strings.SplitAfter(result.Content[startIndex:endIndex], "\n")
79-
lines := make([]ResultLine, 0, len(contentLines))
93+
lineNums := make([]int, 0, len(contentLines))
8094
index := startIndex
8195
for i, line := range contentLines {
8296
var err error
@@ -91,37 +105,24 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res
91105
line[closeActiveIndex:],
92106
)
93107
} else {
94-
err = writeStrings(&formattedLinesBuffer,
95-
line,
96-
)
108+
err = writeStrings(&formattedLinesBuffer, line)
97109
}
98110
if err != nil {
99111
return nil, err
100112
}
101113

102-
lines = append(lines, ResultLine{Num: startLineNum + i})
114+
lineNums = append(lineNums, startLineNum+i)
103115
index += len(line)
104116
}
105117

106-
// we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting
107-
hl, _ := highlight.Code(result.Filename, "", formattedLinesBuffer.String())
108-
highlightedLines := strings.Split(string(hl), "\n")
109-
110-
// The lines outputted by highlight.Code might not match the original lines, because "highlight" removes the last `\n`
111-
lines = lines[:min(len(highlightedLines), len(lines))]
112-
highlightedLines = highlightedLines[:len(lines)]
113-
for i := 0; i < len(lines); i++ {
114-
lines[i].FormattedContent = template.HTML(highlightedLines[i])
115-
}
116-
117118
return &Result{
118119
RepoID: result.RepoID,
119120
Filename: result.Filename,
120121
CommitID: result.CommitID,
121122
UpdatedUnix: result.UpdatedUnix,
122123
Language: result.Language,
123124
Color: result.Color,
124-
Lines: lines,
125+
Lines: HighlightSearchResultCode(result.Filename, lineNums, formattedLinesBuffer.String()),
125126
}, nil
126127
}
127128

routers/web/repo/search.go

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ package repo
55

66
import (
77
"net/http"
8+
"strings"
89

910
"code.gitea.io/gitea/models/db"
1011
"code.gitea.io/gitea/modules/base"
12+
"code.gitea.io/gitea/modules/git"
1113
code_indexer "code.gitea.io/gitea/modules/indexer/code"
1214
"code.gitea.io/gitea/modules/setting"
1315
"code.gitea.io/gitea/services/context"
@@ -17,11 +19,6 @@ const tplSearch base.TplName = "repo/search"
1719

1820
// Search render repository search page
1921
func Search(ctx *context.Context) {
20-
if !setting.Indexer.RepoIndexerEnabled {
21-
ctx.Redirect(ctx.Repo.RepoLink)
22-
return
23-
}
24-
2522
language := ctx.FormTrim("l")
2623
keyword := ctx.FormTrim("q")
2724

@@ -42,24 +39,51 @@ func Search(ctx *context.Context) {
4239
page = 1
4340
}
4441

45-
total, searchResults, searchResultLanguages, err := code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{
46-
RepoIDs: []int64{ctx.Repo.Repository.ID},
47-
Keyword: keyword,
48-
IsKeywordFuzzy: isFuzzy,
49-
Language: language,
50-
Paginator: &db.ListOptions{
51-
Page: page,
52-
PageSize: setting.UI.RepoSearchPagingNum,
53-
},
54-
})
55-
if err != nil {
56-
if code_indexer.IsAvailable(ctx) {
57-
ctx.ServerError("SearchResults", err)
58-
return
42+
var total int
43+
var searchResults []*code_indexer.Result
44+
var searchResultLanguages []*code_indexer.SearchResultLanguages
45+
if setting.Indexer.RepoIndexerEnabled {
46+
var err error
47+
total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, &code_indexer.SearchOptions{
48+
RepoIDs: []int64{ctx.Repo.Repository.ID},
49+
Keyword: keyword,
50+
IsKeywordFuzzy: isFuzzy,
51+
Language: language,
52+
Paginator: &db.ListOptions{
53+
Page: page,
54+
PageSize: setting.UI.RepoSearchPagingNum,
55+
},
56+
})
57+
if err != nil {
58+
if code_indexer.IsAvailable(ctx) {
59+
ctx.ServerError("SearchResults", err)
60+
return
61+
}
62+
ctx.Data["CodeIndexerUnavailable"] = true
63+
} else {
64+
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
5965
}
60-
ctx.Data["CodeIndexerUnavailable"] = true
6166
} else {
62-
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
67+
res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{ContextLineNumber: 3, IsFuzzy: isFuzzy})
68+
if err != nil {
69+
ctx.ServerError("GrepSearch", err)
70+
return
71+
}
72+
total = len(res)
73+
pageStart := min((page-1)*setting.UI.RepoSearchPagingNum, len(res))
74+
pageEnd := min(page*setting.UI.RepoSearchPagingNum, len(res))
75+
res = res[pageStart:pageEnd]
76+
for _, r := range res {
77+
searchResults = append(searchResults, &code_indexer.Result{
78+
RepoID: ctx.Repo.Repository.ID,
79+
Filename: r.Filename,
80+
CommitID: ctx.Repo.CommitID,
81+
// UpdatedUnix: not supported yet
82+
// Language: not supported yet
83+
// Color: not supported yet
84+
Lines: code_indexer.HighlightSearchResultCode(r.Filename, r.LineNumbers, strings.Join(r.LineCodes, "\n")),
85+
})
86+
}
6387
}
6488

6589
ctx.Data["Repo"] = ctx.Repo.Repository

templates/repo/home.tmpl

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,18 @@
55
{{template "base/alert" .}}
66
{{template "repo/code/recently_pushed_new_branches" .}}
77
{{if and (not .HideRepoInfo) (not .IsBlame)}}
8-
<div class="ui repo-description gt-word-break">
9-
<div id="repo-desc" class="gt-font-16">
8+
<div class="repo-description">
9+
<div id="repo-desc" class="gt-word-break gt-font-16">
1010
{{$description := .Repository.DescriptionHTML $.Context}}
1111
{{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}}
1212
<a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
1313
</div>
14-
{{if .RepoSearchEnabled}}
15-
<div class="ui repo-search">
16-
<form class="ui form ignore-dirty" action="{{.RepoLink}}/search" method="get">
17-
<div class="field">
18-
<div class="ui small action input{{if .CodeIndexerUnavailable}} disabled left icon{{end}}"{{if .CodeIndexerUnavailable}} data-tooltip-content="{{ctx.Locale.Tr "search.code_search_unavailable"}}"{{end}}>
19-
<input name="q" value="{{.Keyword}}"{{if .CodeIndexerUnavailable}} disabled{{end}} placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
20-
{{if .CodeIndexerUnavailable}}
21-
<i class="icon">{{svg "octicon-alert"}}</i>
22-
{{end}}
23-
{{template "shared/search/button" dict "Disabled" .CodeIndexerUnavailable}}
24-
</div>
25-
</div>
26-
</form>
14+
<form class="ignore-dirty" action="{{.RepoLink}}/search" method="get">
15+
<div class="ui small action input">
16+
<input name="q" value="{{.Keyword}}" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
17+
{{template "shared/search/button"}}
2718
</div>
28-
{{end}}
19+
</form>
2920
</div>
3021
<div class="gt-df gt-ac gt-fw gt-gap-2" id="repo-topics">
3122
{{range .Topics}}<a class="ui repo-topic large label topic gt-m-0" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}

templates/shared/searchbottom.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{{if or .result.Language (not .result.UpdatedUnix.IsZero)}}
12
<div class="ui bottom attached table segment gt-df gt-ac gt-sb">
23
<div class="gt-df gt-ac gt-ml-4">
34
{{if .result.Language}}
@@ -10,3 +11,4 @@
1011
{{end}}
1112
</div>
1213
</div>
14+
{{end}}

0 commit comments

Comments
 (0)