diff options
-rw-r--r-- | README.md | 32 | ||||
-rw-r--r-- | options.go | 8 | ||||
-rw-r--r-- | worktree.go | 84 | ||||
-rw-r--r-- | worktree_test.go | 67 |
4 files changed, 138 insertions, 53 deletions
@@ -1,18 +1,19 @@ -# go-git [![GoDoc](https://godoc.org/gopkg.in/src-d/go-git.v4?status.svg)](https://godoc.org/github.com/src-d/go-git) [![Build Status](https://travis-ci.org/src-d/go-git.svg)](https://travis-ci.org/src-d/go-git) [![Build status](https://ci.appveyor.com/api/projects/status/nyidskwifo4py6ub?svg=true)](https://ci.appveyor.com/project/mcuadros/go-git) [![codecov.io](https://codecov.io/github/src-d/go-git/coverage.svg)](https://codecov.io/github/src-d/go-git) [![codebeat badge](https://codebeat.co/badges/b6cb2f73-9e54-483d-89f9-4b95a911f40c)](https://codebeat.co/projects/github-com-src-d-go-git) +![go-git logo](https://cdn.rawgit.com/src-d/artwork/02036484/go-git/files/go-git-github-readme-header.png) +[![GoDoc](https://godoc.org/gopkg.in/src-d/go-git.v4?status.svg)](https://godoc.org/github.com/src-d/go-git) [![Build Status](https://travis-ci.org/src-d/go-git.svg)](https://travis-ci.org/src-d/go-git) [![Build status](https://ci.appveyor.com/api/projects/status/nyidskwifo4py6ub?svg=true)](https://ci.appveyor.com/project/mcuadros/go-git) [![codecov.io](https://codecov.io/github/src-d/go-git/coverage.svg)](https://codecov.io/github/src-d/go-git) [![Go Report Card](https://goreportcard.com/badge/github.com/src-d/go-git)](https://goreportcard.com/report/github.com/src-d/go-git) -A highly extensible git implementation in **pure Go**. +*go-git* is a highly extensible git implementation library written in **pure Go**. -*go-git* aims to reach the completeness of [libgit2](https://libgit2.github.com/) or [jgit](http://www.eclipse.org/jgit/), nowadays covers the **majority** of the plumbing **read operations** and **some** of the main **write operations**, but lacks the main porcelain operations such as merges. +It can be used to manipulate git repositories at low level *(plumbing)* or high level *(porcelain)*, through an idiomatic Go API. It also supports several type of storage, such as in-memory filesystems, or custom implementations thanks to the [`Storer`](https://godoc.org/gopkg.in/src-d/go-git.v4/plumbing/storer) interface. -It is **highly extensible**, we have been following the open/close principle in its design to facilitate extensions, mainly focusing the efforts on the persistence of the objects. +It's being actively develop since 2015 and is being use extensively by [source{d}](https://sourced.tech/) and [Keybase](https://keybase.io/blog/encrypted-git-for-everyone), and by many other libraries and tools. -### ... is this production ready? +Comparison with git +------------------- -The master branch represents the `v4` of the library, it is currently under active development and is planned to be released in early 2017. +*go-git* aims to be fully compatible with [git](https://github.com/git/git), all the *porcelain* operations are implemented to work exactly as *git* does. -If you are looking for a production ready version, please take a look to the [`v3`](https://github.com/src-d/go-git/tree/v3) which is being used in production at [source{d}](http://sourced.tech) since August 2015 to analyze all GitHub public repositories (i.e. 16M repositories). +*git* is a humongous project with years of development by thousands of contributors, making it challenging for *go-git* implement all the features. You can find a comparison of *go-git* vs *git* in the [compatibility documentation](COMPATIBILITY.md). -We recommend the use of `v4` to develop new projects since it includes much new functionality and provides a more *idiomatic git* API Installation ------------ @@ -23,6 +24,7 @@ The recommended way to install *go-git* is: go get -u gopkg.in/src-d/go-git.v4/... ``` +> We use [gopkg.in](http://labix.org/gopkg.in) for having a versioned API, this means that when `go get` clones the package, is the latest tag matching `v4.*` cloned and not the master branch. Examples -------- @@ -108,20 +110,14 @@ Date: Fri Nov 11 13:23:22 2016 +0100 ... ``` -You can find this [example](_examples/log/main.go) and many other at the [examples](_examples) folder - -Comparison With Git -------------------- - -In the [compatibility documentation](COMPATIBILITY.md) you can find a comparison -table of git with go-git. +You can find this [example](_examples/log/main.go) and many others at the [examples](_examples) folder Contribute ---------- -If you are interested in contributing to go-git, open an [issue](https://github.com/src-d/go-git/issues) explaining which missing functionality you want to work on, and we will guide you through the implementation. +[Contributions](https://github.com/src-d/go-git/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) are more than welcome, if you are interested please take a look to +our [Contributing Guidelines](CONTRIBUTING.md). License ------- - -MIT, see [LICENSE](LICENSE) +Apache License Version 2.0, see [LICENSE](LICENSE) @@ -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 ( diff --git a/worktree.go b/worktree.go index 2c35ffb..a23397e 100644 --- a/worktree.go +++ b/worktree.go @@ -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 { diff --git a/worktree_test.go b/worktree_test.go index ee9bf2f..e51e89a 100644 --- a/worktree_test.go +++ b/worktree_test.go @@ -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{ { @@ -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{ { @@ -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{ @@ -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{ @@ -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{ { @@ -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{ @@ -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", + }, + }, }, } |