mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
3.9 KiB
149 lines
3.9 KiB
// Copyright 2024 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package git |
|
|
|
import ( |
|
"bufio" |
|
"bytes" |
|
"context" |
|
"errors" |
|
"fmt" |
|
"os" |
|
"slices" |
|
"strconv" |
|
"strings" |
|
|
|
"code.gitea.io/gitea/modules/git/gitcmd" |
|
"code.gitea.io/gitea/modules/util" |
|
) |
|
|
|
type GrepResult struct { |
|
Filename string |
|
LineNumbers []int |
|
LineCodes []string |
|
} |
|
|
|
type GrepModeType string |
|
|
|
const ( |
|
GrepModeExact GrepModeType = "exact" |
|
GrepModeWords GrepModeType = "words" |
|
GrepModeRegexp GrepModeType = "regexp" |
|
) |
|
|
|
type GrepOptions struct { |
|
RefName string |
|
MaxResultLimit int |
|
ContextLineNumber int |
|
GrepMode GrepModeType |
|
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) { |
|
stdoutReader, stdoutWriter, err := os.Pipe() |
|
if err != nil { |
|
return nil, fmt.Errorf("unable to create os pipe to grep: %w", err) |
|
} |
|
defer func() { |
|
_ = stdoutReader.Close() |
|
_ = stdoutWriter.Close() |
|
}() |
|
|
|
/* |
|
The output is like this ( "^@" means \x00): |
|
|
|
HEAD:.air.toml |
|
6^@bin = "gitea" |
|
|
|
HEAD:.changelog.yml |
|
2^@repo: go-gitea/gitea |
|
*/ |
|
var results []*GrepResult |
|
cmd := gitcmd.NewCommand("grep", "--null", "--break", "--heading", "--line-number", "--full-name") |
|
cmd.AddOptionValues("--context", strconv.Itoa(opts.ContextLineNumber)) |
|
switch opts.GrepMode { |
|
case GrepModeExact: |
|
cmd.AddArguments("--fixed-strings") |
|
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-")) |
|
case GrepModeRegexp: |
|
cmd.AddArguments("--perl-regexp") |
|
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-")) |
|
default: /* words */ |
|
words := strings.Fields(search) |
|
cmd.AddArguments("--fixed-strings", "--ignore-case") |
|
for i, word := range words { |
|
cmd.AddOptionValues("-e", strings.TrimLeft(word, "-")) |
|
if i < len(words)-1 { |
|
cmd.AddOptionValues("--and") |
|
} |
|
} |
|
} |
|
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD")) |
|
cmd.AddDashesAndList(opts.PathspecList...) |
|
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50) |
|
stderr := bytes.Buffer{} |
|
err = cmd.WithDir(repo.Path). |
|
WithStdout(stdoutWriter). |
|
WithStderr(&stderr). |
|
WithPipelineFunc(func(ctx context.Context, cancel context.CancelFunc) error { |
|
_ = stdoutWriter.Close() |
|
defer stdoutReader.Close() |
|
|
|
isInBlock := false |
|
rd := bufio.NewReaderSize(stdoutReader, util.IfZero(opts.MaxLineLength, 16*1024)) |
|
var res *GrepResult |
|
for { |
|
lineBytes, isPrefix, err := rd.ReadLine() |
|
if isPrefix { |
|
lineBytes = slices.Clone(lineBytes) |
|
for isPrefix && err == nil { |
|
_, isPrefix, err = rd.ReadLine() |
|
} |
|
} |
|
if len(lineBytes) == 0 && err != nil { |
|
break |
|
} |
|
line := string(lineBytes) // the memory of lineBytes is mutable |
|
if !isInBlock { |
|
if _ /* ref */, filename, ok := strings.Cut(line, ":"); ok { |
|
isInBlock = true |
|
res = &GrepResult{Filename: filename} |
|
results = append(results, res) |
|
} |
|
continue |
|
} |
|
if line == "" { |
|
if len(results) >= opts.MaxResultLimit { |
|
cancel() |
|
break |
|
} |
|
isInBlock = false |
|
continue |
|
} |
|
if line == "--" { |
|
continue |
|
} |
|
if lineNum, lineCode, ok := strings.Cut(line, "\x00"); ok { |
|
lineNumInt, _ := strconv.Atoi(lineNum) |
|
res.LineNumbers = append(res.LineNumbers, lineNumInt) |
|
res.LineCodes = append(res.LineCodes, lineCode) |
|
} |
|
} |
|
return nil |
|
}). |
|
Run(ctx) |
|
// git grep exits by cancel (killed), usually it is caused by the limit of results |
|
if gitcmd.IsErrorExitCode(err, -1) && stderr.Len() == 0 { |
|
return results, nil |
|
} |
|
// git grep exits with 1 if no results are found |
|
if gitcmd.IsErrorExitCode(err, 1) && stderr.Len() == 0 { |
|
return nil, nil |
|
} |
|
if err != nil && !errors.Is(err, context.Canceled) { |
|
return nil, fmt.Errorf("unable to run git grep: %w, stderr: %s", err, stderr.String()) |
|
} |
|
return results, nil |
|
}
|
|
|