Skip to content

Commit

Permalink
git: Worktree.Grep() support multiple patterns and pathspecs
Browse files Browse the repository at this point in the history
Signed-off-by: Sunny <[email protected]>
  • Loading branch information
darkowlzz committed Dec 19, 2017
1 parent 757a260 commit 4ead334
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 35 deletions.
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

0 comments on commit 4ead334

Please sign in to comment.