git-module

git clone git://git.lin.moe/fork/git-module.git

  1package git
  2
  3import (
  4	"context"
  5	"fmt"
  6	"strconv"
  7	"strings"
  8)
  9
 10// GrepOptions contains optional arguments for grep search over repository files.
 11//
 12// Docs: https://git-scm.com/docs/git-grep
 13type GrepOptions struct {
 14	// The tree to run the search. Defaults to "HEAD".
 15	Tree string
 16	// Limits the search to files in the specified pathspec.
 17	Pathspec string
 18	// Whether to do case insensitive search.
 19	IgnoreCase bool
 20	// Whether to match the pattern only at word boundaries.
 21	WordRegexp bool
 22	// Whether use extended regular expressions.
 23	ExtendedRegexp bool
 24	CommandOptions
 25}
 26
 27// 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 string
 31	// The path of the file that matched.
 32	Path string
 33	// The line number of the match.
 34	Line int
 35	// The 1-indexed column number of the match.
 36	Column int
 37	// The text of the line that matched.
 38	Text string
 39}
 40
 41func parseGrepLine(line string) (*GrepResult, error) {
 42	r := &GrepResult{}
 43	sp := strings.SplitN(line, ":", 5)
 44	var n int
 45	switch len(sp) {
 46	case 4:
 47		// HEAD
 48		r.Tree = "HEAD"
 49	case 5:
 50		// Tree included
 51		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, nil
 64}
 65
 66// 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 GrepOptions
 69	if len(opts) > 0 {
 70		opt = opts[0]
 71	}
 72	if opt.Tree == "" {
 73		opt.Tree = "HEAD"
 74	}
 75
 76	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	}
 91
 92	stdout, err := exec(ctx, r.path, args, opt.Envs)
 93	if err != nil {
 94		return nil
 95	}
 96
 97	var results []*GrepResult
 98	// Normalize line endings
 99	lines := strings.Split(strings.ReplaceAll(string(stdout), "\r", ""), "\n")
100	for _, line := range lines {
101		if len(line) == 0 {
102			continue
103		}
104		r, err := parseGrepLine(line)
105		if err == nil {
106			results = append(results, r)
107		}
108	}
109	return results
110}