git-lfs/filepathfilter/filepathfilter.go
brian m. carlson 3de52d4037
filepathfilter: don't say file is both accepted and rejected
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.
2018-11-02 18:40:15 +00:00

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
}