git-lfs/filepathfilter/filepathfilter.go
Chris Darroch 04abbd8436 make additional message strings translatable
Following on from the changes in PR #4781, we can make
additional message strings translatable using the
tr.Tr.Get() method.

Because this method performs printf(3)-style format string
parsing and interpolation, we can simplify some of the
surrounding calls, e.g., from fmt.Errorf() to errors.New(),
and from fmt.Fprintf() to fmt.Fprintln().  This ensures
that if either the translated text or any interpolated
arguments happen to contain character sequences that would
be interpreted as Go format specifiers (e.g., "%s" or "%d"),
these will not result in warnings such as "%!s(MISSING)"
in the output text.

Note also that we try to remove newlines from the message
strings were possible and change the surrounding calls to
append them instead, e.g., with fmt.Fprintln().
2022-01-29 22:36:19 -08:00

191 lines
4.4 KiB
Go

package filepathfilter
import (
"strings"
"github.com/git-lfs/git-lfs/v3/tr"
"github.com/git-lfs/wildmatch/v2"
"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
defaultValue bool
}
type PatternType bool
const (
GitIgnore = PatternType(false)
GitAttributes = PatternType(true)
)
func (p PatternType) String() string {
if p == GitIgnore {
return "gitignore"
}
return "gitattributes"
}
type options struct {
defaultValue bool
}
type option func(*options)
// DefaultValue is an option representing the default value of a filepathfilter
// if no patterns match. If this option is not provided, the default is true.
func DefaultValue(val bool) option {
return func(args *options) {
args.defaultValue = val
}
}
func NewFromPatterns(include, exclude []Pattern, setters ...option) *Filter {
args := &options{defaultValue: true}
for _, setter := range setters {
setter(args)
}
return &Filter{include: include, exclude: exclude, defaultValue: args.defaultValue}
}
func New(include, exclude []string, ptype PatternType, setters ...option) *Filter {
return NewFromPatterns(
convertToWildmatch(include, ptype),
convertToWildmatch(exclude, ptype), setters...)
}
// 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
}
// Beyond this point, the only values we can logically return are false
// or the default value. If the default is false, then there's no point
// traversing the exclude patterns because the return value will always
// be false; we'd do extra work for no functional benefit.
if !included && !f.defaultValue {
tracerx.Printf("filepathfilter: rejecting %q", filename)
return false
}
for _, ex := range f.exclude {
if ex.Match(filename) {
tracerx.Printf("filepathfilter: rejecting %q via %q", filename, ex.String())
return false
}
}
// No patterns matched and our default value is true.
tracerx.Printf("filepathfilter: accepting %q", filename)
return true
}
type wm struct {
w *wildmatch.Wildmatch
p string
}
func (w *wm) Match(filename string) bool {
return w.w.Match(filename)
}
func (w *wm) String() string {
return w.p
}
const (
sep byte = '/'
)
func NewPattern(p string, ptype PatternType) Pattern {
tracerx.Printf("filepathfilter: creating pattern %q of type %v", p, ptype)
switch ptype {
case GitIgnore:
return &wm{
p: p,
w: wildmatch.NewWildmatch(
p,
wildmatch.SystemCase,
wildmatch.Contents,
),
}
case GitAttributes:
return &wm{
p: p,
w: wildmatch.NewWildmatch(
p,
wildmatch.SystemCase,
wildmatch.Basename,
wildmatch.GitAttributes,
),
}
default:
panic(tr.Tr.Get("unreachable"))
}
}
// 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, ptype PatternType) []Pattern {
patterns := make([]Pattern, len(rawpatterns))
for i, raw := range rawpatterns {
patterns[i] = NewPattern(raw, ptype)
}
return patterns
}