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 }