1package git23import (4 "context"5 "fmt"6 "strconv"7 "strings"8)910// GrepOptions contains optional arguments for grep search over repository files.11//12// Docs: https://git-scm.com/docs/git-grep13type GrepOptions struct {14 // The tree to run the search. Defaults to "HEAD".15 Tree string16 // Limits the search to files in the specified pathspec.17 Pathspec string18 // Whether to do case insensitive search.19 IgnoreCase bool20 // Whether to match the pattern only at word boundaries.21 WordRegexp bool22 // Whether use extended regular expressions.23 ExtendedRegexp bool24 CommandOptions25}2627// GrepResult represents a single result from a grep search.28type GrepResult struct {29 // The tree of the file that matched, e.g. "HEAD".30 Tree string31 // The path of the file that matched.32 Path string33 // The line number of the match.34 Line int35 // The 1-indexed column number of the match.36 Column int37 // The text of the line that matched.38 Text string39}4041func parseGrepLine(line string) (*GrepResult, error) {42 r := &GrepResult{}43 sp := strings.SplitN(line, ":", 5)44 var n int45 switch len(sp) {46 case 4:47 // HEAD48 r.Tree = "HEAD"49 case 5:50 // Tree included51 r.Tree = sp[0]52 n++53 default:54 return nil, fmt.Errorf("invalid grep line: %s", line)55 }56 r.Path = sp[n]57 n++58 r.Line, _ = strconv.Atoi(sp[n])59 n++60 r.Column, _ = strconv.Atoi(sp[n])61 n++62 r.Text = sp[n]63 return r, nil64}6566// Grep returns the results of a grep search in the repository.67func (r *Repository) Grep(ctx context.Context, pattern string, opts ...GrepOptions) []*GrepResult {68 var opt GrepOptions69 if len(opts) > 0 {70 opt = opts[0]71 }72 if opt.Tree == "" {73 opt.Tree = "HEAD"74 }7576 args := []string{"grep"}77 args = append(args, "--full-name", "--line-number", "--column")78 if opt.IgnoreCase {79 args = append(args, "--ignore-case")80 }81 if opt.WordRegexp {82 args = append(args, "--word-regexp")83 }84 if opt.ExtendedRegexp {85 args = append(args, "--extended-regexp")86 }87 args = append(args, "--end-of-options", pattern, opt.Tree)88 if opt.Pathspec != "" {89 args = append(args, "--", opt.Pathspec)90 }9192 stdout, err := exec(ctx, r.path, args, opt.Envs)93 if err != nil {94 return nil95 }9697 var results []*GrepResult98 // Normalize line endings99 lines := strings.Split(strings.ReplaceAll(string(stdout), "\r", ""), "\n")100 for _, line := range lines {101 if len(line) == 0 {102 continue103 }104 r, err := parseGrepLine(line)105 if err == nil {106 results = append(results, r)107 }108 }109 return results110}