Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

git: Worktree.Grep() support multiple patterns and pathspecs #695

Merged
merged 1 commit into from
Dec 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,16 +369,16 @@ type CleanOptions struct {

// GrepOptions describes how a grep should be performed.
type GrepOptions struct {
// Pattern is a compiled Regexp object to be matched.
Pattern *regexp.Regexp
// Patterns are compiled Regexp objects to be matched.
Patterns []*regexp.Regexp
// InvertMatch selects non-matching lines.
InvertMatch bool
// CommitHash is the hash of the commit from which worktree should be derived.
CommitHash plumbing.Hash
// ReferenceName is the branch or tag name from which worktree should be derived.
ReferenceName plumbing.ReferenceName
// PathSpec is a compiled Regexp object of pathspec to use in the matching.
PathSpec *regexp.Regexp
// PathSpecs are compiled Regexp objects of pathspec to use in the matching.
PathSpecs []*regexp.Regexp
}

var (
Expand Down
84 changes: 61 additions & 23 deletions worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -765,50 +765,88 @@ func (w *Worktree) Grep(opts *GrepOptions) ([]GrepResult, error) {

// findMatchInFiles takes a FileIter, worktree name and GrepOptions, and
// returns a slice of GrepResult containing the result of regex pattern matching
// in the file content.
// in content of all the files.
func findMatchInFiles(fileiter *object.FileIter, treeName string, opts *GrepOptions) ([]GrepResult, error) {
var results []GrepResult

// Iterate through the files and look for any matches.
err := fileiter.ForEach(func(file *object.File) error {
// Check if the file name matches with the pathspec.
if opts.PathSpec != nil && !opts.PathSpec.MatchString(file.Name) {
var fileInPathSpec bool

// When no pathspecs are provided, search all the files.
if len(opts.PathSpecs) == 0 {
fileInPathSpec = true
}

// Check if the file name matches with the pathspec. Break out of the
// loop once a match is found.
for _, pathSpec := range opts.PathSpecs {
if pathSpec != nil && pathSpec.MatchString(file.Name) {
fileInPathSpec = true
break
}
}

// If the file does not match with any of the pathspec, skip it.
if !fileInPathSpec {
return nil
}

content, err := file.Contents()
grepResults, err := findMatchInFile(file, treeName, opts)
if err != nil {
return err
}
results = append(results, grepResults...)

return nil
})

return results, err
}

// Split the content and make parseable line-by-line.
contentByLine := strings.Split(content, "\n")
for lineNum, cnt := range contentByLine {
addToResult := false
// Match the pattern and content.
if opts.Pattern != nil && opts.Pattern.MatchString(cnt) {
// findMatchInFile takes a single File, worktree name and GrepOptions,
// and returns a slice of GrepResult containing the result of regex pattern
// matching in the given file.
func findMatchInFile(file *object.File, treeName string, opts *GrepOptions) ([]GrepResult, error) {
var grepResults []GrepResult

content, err := file.Contents()
if err != nil {
return grepResults, err
}

// Split the file content and parse line-by-line.
contentByLine := strings.Split(content, "\n")
for lineNum, cnt := range contentByLine {
addToResult := false

// Match the patterns and content. Break out of the loop once a
// match is found.
for _, pattern := range opts.Patterns {
if pattern != nil && pattern.MatchString(cnt) {
// Add to result only if invert match is not enabled.
if !opts.InvertMatch {
addToResult = true
break
}
} else if opts.InvertMatch {
// If matching fails, and invert match is enabled, add to results.
// If matching fails, and invert match is enabled, add to
// results.
addToResult = true
break
}
}

if addToResult {
results = append(results, GrepResult{
FileName: file.Name,
LineNumber: lineNum + 1,
Content: cnt,
TreeName: treeName,
})
}
if addToResult {
grepResults = append(grepResults, GrepResult{
FileName: file.Name,
LineNumber: lineNum + 1,
Content: cnt,
TreeName: treeName,
})
}
return nil
})
}

return results, err
return grepResults, nil
}

func rmFileAndDirIfEmpty(fs billy.Filesystem, name string) error {
Expand Down
67 changes: 59 additions & 8 deletions worktree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1330,7 +1330,7 @@ func (s *WorktreeSuite) TestGrep(c *C) {
{
name: "basic word match",
options: GrepOptions{
Pattern: regexp.MustCompile("import"),
Patterns: []*regexp.Regexp{regexp.MustCompile("import")},
},
wantResult: []GrepResult{
{
Expand All @@ -1349,7 +1349,7 @@ func (s *WorktreeSuite) TestGrep(c *C) {
}, {
name: "case insensitive match",
options: GrepOptions{
Pattern: regexp.MustCompile(`(?i)IMport`),
Patterns: []*regexp.Regexp{regexp.MustCompile(`(?i)IMport`)},
},
wantResult: []GrepResult{
{
Expand All @@ -1368,7 +1368,7 @@ func (s *WorktreeSuite) TestGrep(c *C) {
}, {
name: "invert match",
options: GrepOptions{
Pattern: regexp.MustCompile("import"),
Patterns: []*regexp.Regexp{regexp.MustCompile("import")},
InvertMatch: true,
},
dontWantResult: []GrepResult{
Expand All @@ -1388,7 +1388,7 @@ func (s *WorktreeSuite) TestGrep(c *C) {
}, {
name: "match at a given commit hash",
options: GrepOptions{
Pattern: regexp.MustCompile("The MIT License"),
Patterns: []*regexp.Regexp{regexp.MustCompile("The MIT License")},
CommitHash: plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"),
},
wantResult: []GrepResult{
Expand All @@ -1410,8 +1410,8 @@ func (s *WorktreeSuite) TestGrep(c *C) {
}, {
name: "match for a given pathspec",
options: GrepOptions{
Pattern: regexp.MustCompile("import"),
PathSpec: regexp.MustCompile("go/"),
Patterns: []*regexp.Regexp{regexp.MustCompile("import")},
PathSpecs: []*regexp.Regexp{regexp.MustCompile("go/")},
},
wantResult: []GrepResult{
{
Expand All @@ -1432,7 +1432,7 @@ func (s *WorktreeSuite) TestGrep(c *C) {
}, {
name: "match at a given reference name",
options: GrepOptions{
Pattern: regexp.MustCompile("import"),
Patterns: []*regexp.Regexp{regexp.MustCompile("import")},
ReferenceName: "refs/heads/master",
},
wantResult: []GrepResult{
Expand All @@ -1446,11 +1446,62 @@ func (s *WorktreeSuite) TestGrep(c *C) {
}, {
name: "ambiguous options",
options: GrepOptions{
Pattern: regexp.MustCompile("import"),
Patterns: []*regexp.Regexp{regexp.MustCompile("import")},
CommitHash: plumbing.NewHash("2d55a722f3c3ecc36da919dfd8b6de38352f3507"),
ReferenceName: "somereferencename",
},
wantError: ErrHashOrReference,
}, {
name: "multiple patterns",
options: GrepOptions{
Patterns: []*regexp.Regexp{
regexp.MustCompile("import"),
regexp.MustCompile("License"),
},
},
wantResult: []GrepResult{
{
FileName: "go/example.go",
LineNumber: 3,
Content: "import (",
TreeName: "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
},
{
FileName: "vendor/foo.go",
LineNumber: 3,
Content: "import \"fmt\"",
TreeName: "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
},
{
FileName: "LICENSE",
LineNumber: 1,
Content: "The MIT License (MIT)",
TreeName: "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
},
},
}, {
name: "multiple pathspecs",
options: GrepOptions{
Patterns: []*regexp.Regexp{regexp.MustCompile("import")},
PathSpecs: []*regexp.Regexp{
regexp.MustCompile("go/"),
regexp.MustCompile("vendor/"),
},
},
wantResult: []GrepResult{
{
FileName: "go/example.go",
LineNumber: 3,
Content: "import (",
TreeName: "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
},
{
FileName: "vendor/foo.go",
LineNumber: 3,
Content: "import \"fmt\"",
TreeName: "6ecf0ef2c2dffb796033e5a02219af86ec6584e5",
},
},
},
}

Expand Down