introduce pattern type so regexes are built up front

This commit is contained in:
risk danger olson 2016-11-21 14:34:45 -07:00
parent 43cfd23fe9
commit dfbc31d148
2 changed files with 135 additions and 7 deletions

@ -1,20 +1,149 @@
package filepathfilter
import "github.com/git-lfs/git-lfs/tools"
import (
"fmt"
"path/filepath"
"regexp"
"strings"
"github.com/git-lfs/git-lfs/tools"
)
type pattern interface {
Match(filename string) bool
}
type Filter struct {
include []string
exclude []string
include []pattern
exclude []pattern
}
func New(include, exclude []string) *Filter {
return &Filter{include: include, exclude: exclude}
return &Filter{
include: convertToPatterns(include),
exclude: convertToPatterns(exclude),
}
}
func (f *Filter) Allows(path string) bool {
func (f *Filter) Allows(filename string) bool {
if f == nil {
return true
}
return tools.FilenamePassesIncludeExcludeFilter(path, f.include, f.exclude)
if len(f.include)+len(f.exclude) == 0 {
return true
}
cleanedName := filepath.Clean(filename)
if len(f.include) > 0 {
matched := false
for _, inc := range f.include {
matched = inc.Match(cleanedName)
if matched {
break
}
}
if !matched {
return false
}
}
if len(f.exclude) > 0 {
for _, ex := range f.exclude {
if ex.Match(cleanedName) {
return false
}
}
}
return true
}
func convertToPatterns(rawpatterns []string) []pattern {
patterns := make([]pattern, len(rawpatterns))
noop := noOpMatcher{}
for i, raw := range rawpatterns {
cleaned := filepath.Clean(raw)
// Special case local dir, matches all (inc subpaths)
if _, local := localDirSet[cleaned]; local {
patterns[i] = noop
continue
}
hasPathSep := strings.Contains(cleaned, string(filepath.Separator))
// special case * when there are no path separators
// filepath.Match never allows * to match a path separator, which is correct
// for gitignore IF the pattern includes a path separator, but not otherwise
// So *.txt should match in any subdir, as should test*, but sub/*.txt would
// only match directly in the sub dir
// Don't need to test cross-platform separators as both cleaned above
if !hasPathSep && strings.Contains(cleaned, "*") {
pattern := regexp.QuoteMeta(cleaned)
regpattern := fmt.Sprintf("^%s$", strings.Replace(pattern, "\\*", ".*", -1))
patterns[i] = &pathlessWildcardPattern{
rawPattern: cleaned,
wildcardRE: regexp.MustCompile(regpattern),
}
// Also support ** with path separators
} else if hasPathSep && strings.Contains(cleaned, "**") {
pattern := regexp.QuoteMeta(cleaned)
regpattern := fmt.Sprintf("^%s$", strings.Replace(pattern, "\\*\\*", ".*", -1))
patterns[i] = &doubleWildcardPattern{
rawPattern: cleaned,
wildcardRE: regexp.MustCompile(regpattern),
}
} else {
patterns[i] = &basicPattern{rawPattern: cleaned}
}
}
return patterns
}
type basicPattern struct {
rawPattern string
}
// Match is a revised version of filepath.Match which makes it behave more
// like gitignore
func (p *basicPattern) Match(name string) bool {
matched, _ := filepath.Match(p.rawPattern, name)
// Also support matching a parent directory without a wildcard
return matched || strings.HasPrefix(name, p.rawPattern+string(filepath.Separator))
}
type pathlessWildcardPattern struct {
rawPattern string
wildcardRE *regexp.Regexp
}
// Match is a revised version of filepath.Match which makes it behave more
// like gitignore
func (p *pathlessWildcardPattern) Match(name string) bool {
matched, _ := filepath.Match(p.rawPattern, name)
// Match the whole of the base name but allow matching in folders if no path
return matched || p.wildcardRE.MatchString(filepath.Base(name))
}
type doubleWildcardPattern struct {
rawPattern string
wildcardRE *regexp.Regexp
}
// Match is a revised version of filepath.Match which makes it behave more
// like gitignore
func (p *doubleWildcardPattern) Match(name string) bool {
matched, _ := filepath.Match(p.rawPattern, name)
// Match the whole of the base name but allow matching in folders if no path
return matched || p.wildcardRE.MatchString(name)
}
type noOpMatcher struct {
}
func (n noOpMatcher) Match(name string) bool {
return true
}
var localDirSet = tools.NewStringSetFromSlice([]string{".", "./", ".\\"})

@ -262,7 +262,6 @@ func fastWalkFromRoot(dir string, excludeFilename string,
waitg.Wait()
close(fiChan)
close(errChan)
}
// fastWalkFileOrDir is the main recursive implementation of fast walk