3de52d4037
The file path filter can mark a file as either accepted or rejected. However, one of the messages that says that a file was rejected is in the main code path, and is therefore always executed, even if the file is actually accepted. This leads to contradictory messages and noisier output. Ensure that we indicate that a file is rejected by the filter only if it is indeed rejected; otherwise, say only that it is accepted.
176 lines
3.9 KiB
Go
176 lines
3.9 KiB
Go
package filepathfilter
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/git-lfs/wildmatch"
|
|
"github.com/rubyist/tracerx"
|
|
)
|
|
|
|
type Pattern interface {
|
|
Match(filename string) bool
|
|
// String returns a string representation (see: regular expressions) of
|
|
// the underlying pattern used to match filenames against this Pattern.
|
|
String() string
|
|
}
|
|
|
|
type Filter struct {
|
|
include []Pattern
|
|
exclude []Pattern
|
|
}
|
|
|
|
func NewFromPatterns(include, exclude []Pattern) *Filter {
|
|
return &Filter{include: include, exclude: exclude}
|
|
}
|
|
|
|
func New(include, exclude []string) *Filter {
|
|
return NewFromPatterns(
|
|
convertToWildmatch(include),
|
|
convertToWildmatch(exclude))
|
|
}
|
|
|
|
// Include returns the result of calling String() on each Pattern in the
|
|
// include set of this *Filter.
|
|
func (f *Filter) Include() []string { return wildmatchToString(f.include...) }
|
|
|
|
// Exclude returns the result of calling String() on each Pattern in the
|
|
// exclude set of this *Filter.
|
|
func (f *Filter) Exclude() []string { return wildmatchToString(f.exclude...) }
|
|
|
|
// wildmatchToString maps the given set of Pattern's to a string slice by
|
|
// calling String() on each pattern.
|
|
func wildmatchToString(ps ...Pattern) []string {
|
|
s := make([]string, 0, len(ps))
|
|
for _, p := range ps {
|
|
s = append(s, p.String())
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (f *Filter) Allows(filename string) bool {
|
|
if f == nil {
|
|
return true
|
|
}
|
|
|
|
var included bool
|
|
for _, inc := range f.include {
|
|
if included = inc.Match(filename); included {
|
|
break
|
|
}
|
|
}
|
|
|
|
if !included && len(f.include) > 0 {
|
|
tracerx.Printf("filepathfilter: rejecting %q via %v", filename, f.include)
|
|
return false
|
|
}
|
|
|
|
for _, ex := range f.exclude {
|
|
if ex.Match(filename) {
|
|
tracerx.Printf("filepathfilter: rejecting %q via %q", filename, ex.String())
|
|
return false
|
|
}
|
|
}
|
|
|
|
tracerx.Printf("filepathfilter: accepting %q", filename)
|
|
return true
|
|
}
|
|
|
|
type wm struct {
|
|
w *wildmatch.Wildmatch
|
|
p string
|
|
dirs bool
|
|
}
|
|
|
|
func (w *wm) Match(filename string) bool {
|
|
return w.w.Match(w.chomp(filename))
|
|
}
|
|
|
|
func (w *wm) chomp(filename string) string {
|
|
return strings.TrimSuffix(filename, string(filepath.Separator))
|
|
}
|
|
|
|
func (w *wm) String() string {
|
|
return w.p
|
|
}
|
|
|
|
const (
|
|
sep byte = '/'
|
|
)
|
|
|
|
func NewPattern(p string) Pattern {
|
|
pp := p
|
|
|
|
// Special case: the below patterns match anything according to existing
|
|
// behavior.
|
|
switch pp {
|
|
case `*`, `.`, `./`, `.\`:
|
|
pp = join("**", "*")
|
|
}
|
|
|
|
dirs := strings.Contains(pp, string(sep))
|
|
rooted := strings.HasPrefix(pp, string(sep))
|
|
wild := strings.Contains(pp, "*")
|
|
|
|
if !dirs && !wild {
|
|
// Special case: if pp is a literal string (optionally including
|
|
// a character class), rewrite it is a substring match.
|
|
pp = join("**", pp, "**")
|
|
} else {
|
|
if dirs && !rooted {
|
|
// Special case: if there are any directory separators,
|
|
// rewrite "pp" as a substring match.
|
|
if !wild {
|
|
pp = join("**", pp, "**")
|
|
}
|
|
} else {
|
|
if rooted {
|
|
// Special case: if there are not any directory
|
|
// separators, rewrite "pp" as a substring
|
|
// match.
|
|
pp = join(pp, "**")
|
|
} else {
|
|
// Special case: if there are not any directory
|
|
// separators, rewrite "pp" as a substring
|
|
// match.
|
|
pp = join("**", pp)
|
|
}
|
|
}
|
|
}
|
|
tracerx.Printf("filepathfilter: rewrite %q as %q", p, pp)
|
|
|
|
return &wm{
|
|
p: p,
|
|
w: wildmatch.NewWildmatch(
|
|
pp,
|
|
wildmatch.SystemCase,
|
|
),
|
|
dirs: dirs,
|
|
}
|
|
}
|
|
|
|
// join joins path elements together via the separator "sep" and produces valid
|
|
// paths without multiple separators (unless multiple separators were included
|
|
// in the original paths []string).
|
|
func join(paths ...string) string {
|
|
var joined string
|
|
|
|
for i, path := range paths {
|
|
joined = joined + path
|
|
if i != len(paths)-1 && !strings.HasSuffix(path, string(sep)) {
|
|
joined = joined + string(sep)
|
|
}
|
|
}
|
|
|
|
return joined
|
|
}
|
|
|
|
func convertToWildmatch(rawpatterns []string) []Pattern {
|
|
patterns := make([]Pattern, len(rawpatterns))
|
|
for i, raw := range rawpatterns {
|
|
patterns[i] = NewPattern(raw)
|
|
}
|
|
return patterns
|
|
}
|