github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+incompatible/filepathfilter/filepathfilter.go (about)

     1  package filepathfilter
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"regexp"
     7  	"strings"
     8  )
     9  
    10  type Pattern interface {
    11  	Match(filename string) bool
    12  }
    13  
    14  type Filter struct {
    15  	include []Pattern
    16  	exclude []Pattern
    17  }
    18  
    19  func NewFromPatterns(include, exclude []Pattern) *Filter {
    20  	return &Filter{include: include, exclude: exclude}
    21  }
    22  
    23  func New(include, exclude []string) *Filter {
    24  	return NewFromPatterns(convertToPatterns(include), convertToPatterns(exclude))
    25  }
    26  
    27  func (f *Filter) Allows(filename string) bool {
    28  	if f == nil {
    29  		return true
    30  	}
    31  
    32  	if len(f.include)+len(f.exclude) == 0 {
    33  		return true
    34  	}
    35  
    36  	cleanedName := filepath.Clean(filename)
    37  
    38  	if len(f.include) > 0 {
    39  		matched := false
    40  		for _, inc := range f.include {
    41  			matched = inc.Match(cleanedName)
    42  			if matched {
    43  				break
    44  			}
    45  		}
    46  		if !matched {
    47  			return false
    48  		}
    49  	}
    50  
    51  	if len(f.exclude) > 0 {
    52  		for _, ex := range f.exclude {
    53  			if ex.Match(cleanedName) {
    54  				return false
    55  			}
    56  		}
    57  	}
    58  
    59  	return true
    60  }
    61  
    62  func NewPattern(rawpattern string) Pattern {
    63  	cleanpattern := filepath.Clean(rawpattern)
    64  
    65  	// Special case local dir, matches all (inc subpaths)
    66  	if _, local := localDirSet[cleanpattern]; local {
    67  		return noOpMatcher{}
    68  	}
    69  
    70  	hasPathSep := strings.Contains(cleanpattern, string(filepath.Separator))
    71  
    72  	// special case * when there are no path separators
    73  	// filepath.Match never allows * to match a path separator, which is correct
    74  	// for gitignore IF the pattern includes a path separator, but not otherwise
    75  	// So *.txt should match in any subdir, as should test*, but sub/*.txt would
    76  	// only match directly in the sub dir
    77  	// Don't need to test cross-platform separators as both cleaned above
    78  	if !hasPathSep && strings.Contains(cleanpattern, "*") {
    79  		pattern := regexp.QuoteMeta(cleanpattern)
    80  		regpattern := fmt.Sprintf("^%s$", strings.Replace(pattern, "\\*", ".*", -1))
    81  		return &pathlessWildcardPattern{
    82  			rawPattern: cleanpattern,
    83  			wildcardRE: regexp.MustCompile(regpattern),
    84  		}
    85  		// Also support ** with path separators
    86  	} else if hasPathSep && strings.Contains(cleanpattern, "**") {
    87  		pattern := regexp.QuoteMeta(cleanpattern)
    88  		regpattern := fmt.Sprintf("^%s$", strings.Replace(pattern, "\\*\\*", ".*", -1))
    89  		return &doubleWildcardPattern{
    90  			rawPattern: cleanpattern,
    91  			wildcardRE: regexp.MustCompile(regpattern),
    92  		}
    93  	} else {
    94  		return &basicPattern{rawPattern: cleanpattern}
    95  	}
    96  }
    97  
    98  func convertToPatterns(rawpatterns []string) []Pattern {
    99  	patterns := make([]Pattern, len(rawpatterns))
   100  	for i, raw := range rawpatterns {
   101  		patterns[i] = NewPattern(raw)
   102  	}
   103  	return patterns
   104  }
   105  
   106  type basicPattern struct {
   107  	rawPattern string
   108  }
   109  
   110  // Match is a revised version of filepath.Match which makes it behave more
   111  // like gitignore
   112  func (p *basicPattern) Match(name string) bool {
   113  	matched, _ := filepath.Match(p.rawPattern, name)
   114  	// Also support matching a parent directory without a wildcard
   115  	return matched || strings.HasPrefix(name, p.rawPattern+string(filepath.Separator))
   116  }
   117  
   118  type pathlessWildcardPattern struct {
   119  	rawPattern string
   120  	wildcardRE *regexp.Regexp
   121  }
   122  
   123  // Match is a revised version of filepath.Match which makes it behave more
   124  // like gitignore
   125  func (p *pathlessWildcardPattern) Match(name string) bool {
   126  	matched, _ := filepath.Match(p.rawPattern, name)
   127  	// Match the whole of the base name but allow matching in folders if no path
   128  	return matched || p.wildcardRE.MatchString(filepath.Base(name))
   129  }
   130  
   131  type doubleWildcardPattern struct {
   132  	rawPattern string
   133  	wildcardRE *regexp.Regexp
   134  }
   135  
   136  // Match is a revised version of filepath.Match which makes it behave more
   137  // like gitignore
   138  func (p *doubleWildcardPattern) Match(name string) bool {
   139  	matched, _ := filepath.Match(p.rawPattern, name)
   140  	// Match the whole of the base name but allow matching in folders if no path
   141  	return matched || p.wildcardRE.MatchString(name)
   142  }
   143  
   144  type noOpMatcher struct {
   145  }
   146  
   147  func (n noOpMatcher) Match(name string) bool {
   148  	return true
   149  }
   150  
   151  var localDirSet = map[string]struct{}{
   152  	".":   struct{}{},
   153  	"./":  struct{}{},
   154  	".\\": struct{}{},
   155  }