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  }