github.com/git-lfs/git-lfs@v2.5.2+incompatible/filepathfilter/filepathfilter.go (about) 1 package filepathfilter 2 3 import ( 4 "path/filepath" 5 "strings" 6 7 "github.com/git-lfs/wildmatch" 8 "github.com/rubyist/tracerx" 9 ) 10 11 type Pattern interface { 12 Match(filename string) bool 13 // String returns a string representation (see: regular expressions) of 14 // the underlying pattern used to match filenames against this Pattern. 15 String() string 16 } 17 18 type Filter struct { 19 include []Pattern 20 exclude []Pattern 21 } 22 23 func NewFromPatterns(include, exclude []Pattern) *Filter { 24 return &Filter{include: include, exclude: exclude} 25 } 26 27 func New(include, exclude []string) *Filter { 28 return NewFromPatterns( 29 convertToWildmatch(include), 30 convertToWildmatch(exclude)) 31 } 32 33 // Include returns the result of calling String() on each Pattern in the 34 // include set of this *Filter. 35 func (f *Filter) Include() []string { return wildmatchToString(f.include...) } 36 37 // Exclude returns the result of calling String() on each Pattern in the 38 // exclude set of this *Filter. 39 func (f *Filter) Exclude() []string { return wildmatchToString(f.exclude...) } 40 41 // wildmatchToString maps the given set of Pattern's to a string slice by 42 // calling String() on each pattern. 43 func wildmatchToString(ps ...Pattern) []string { 44 s := make([]string, 0, len(ps)) 45 for _, p := range ps { 46 s = append(s, p.String()) 47 } 48 49 return s 50 } 51 52 func (f *Filter) Allows(filename string) bool { 53 if f == nil { 54 return true 55 } 56 57 var included bool 58 for _, inc := range f.include { 59 if included = inc.Match(filename); included { 60 break 61 } 62 } 63 64 tracerx.Printf("filepathfilter: rejecting %q via %v", filename, f.include) 65 if !included && len(f.include) > 0 { 66 return false 67 } 68 69 for _, ex := range f.exclude { 70 if ex.Match(filename) { 71 tracerx.Printf("filepathfilter: rejecting %q via %q", filename, ex.String()) 72 return false 73 } 74 } 75 76 tracerx.Printf("filepathfilter: accepting %q", filename) 77 return true 78 } 79 80 type wm struct { 81 w *wildmatch.Wildmatch 82 p string 83 dirs bool 84 } 85 86 func (w *wm) Match(filename string) bool { 87 return w.w.Match(w.chomp(filename)) 88 } 89 90 func (w *wm) chomp(filename string) string { 91 return strings.TrimSuffix(filename, string(filepath.Separator)) 92 } 93 94 func (w *wm) String() string { 95 return w.p 96 } 97 98 const ( 99 sep byte = '/' 100 ) 101 102 func NewPattern(p string) Pattern { 103 pp := p 104 105 // Special case: the below patterns match anything according to existing 106 // behavior. 107 switch pp { 108 case `*`, `.`, `./`, `.\`: 109 pp = join("**", "*") 110 } 111 112 dirs := strings.Contains(pp, string(sep)) 113 rooted := strings.HasPrefix(pp, string(sep)) 114 wild := strings.Contains(pp, "*") 115 116 if !dirs && !wild { 117 // Special case: if pp is a literal string (optionally including 118 // a character class), rewrite it is a substring match. 119 pp = join("**", pp, "**") 120 } else { 121 if dirs && !rooted { 122 // Special case: if there are any directory separators, 123 // rewrite "pp" as a substring match. 124 if !wild { 125 pp = join("**", pp, "**") 126 } 127 } else { 128 if rooted { 129 // Special case: if there are not any directory 130 // separators, rewrite "pp" as a substring 131 // match. 132 pp = join(pp, "**") 133 } else { 134 // Special case: if there are not any directory 135 // separators, rewrite "pp" as a substring 136 // match. 137 pp = join("**", pp) 138 } 139 } 140 } 141 tracerx.Printf("filepathfilter: rewrite %q as %q", p, pp) 142 143 return &wm{ 144 p: p, 145 w: wildmatch.NewWildmatch( 146 pp, 147 wildmatch.SystemCase, 148 ), 149 dirs: dirs, 150 } 151 } 152 153 // join joins path elements together via the separator "sep" and produces valid 154 // paths without multiple separators (unless multiple separators were included 155 // in the original paths []string). 156 func join(paths ...string) string { 157 var joined string 158 159 for i, path := range paths { 160 joined = joined + path 161 if i != len(paths)-1 && !strings.HasSuffix(path, string(sep)) { 162 joined = joined + string(sep) 163 } 164 } 165 166 return joined 167 } 168 169 func convertToWildmatch(rawpatterns []string) []Pattern { 170 patterns := make([]Pattern, len(rawpatterns)) 171 for i, raw := range rawpatterns { 172 patterns[i] = NewPattern(raw) 173 } 174 return patterns 175 }