|
@@ -285,12 +285,23 @@ func (pm *PatternMatcher) Patterns() []*Pattern {
|
|
|
|
|
|
// Pattern defines a single regexp used to filter file paths.
|
|
// Pattern defines a single regexp used to filter file paths.
|
|
type Pattern struct {
|
|
type Pattern struct {
|
|
|
|
+ matchType matchType
|
|
cleanedPattern string
|
|
cleanedPattern string
|
|
dirs []string
|
|
dirs []string
|
|
regexp *regexp.Regexp
|
|
regexp *regexp.Regexp
|
|
exclusion bool
|
|
exclusion bool
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+type matchType int
|
|
|
|
+
|
|
|
|
+const (
|
|
|
|
+ unknownMatch matchType = iota
|
|
|
|
+ exactMatch
|
|
|
|
+ prefixMatch
|
|
|
|
+ suffixMatch
|
|
|
|
+ regexpMatch
|
|
|
|
+)
|
|
|
|
+
|
|
func (p *Pattern) String() string {
|
|
func (p *Pattern) String() string {
|
|
return p.cleanedPattern
|
|
return p.cleanedPattern
|
|
}
|
|
}
|
|
@@ -301,15 +312,31 @@ func (p *Pattern) Exclusion() bool {
|
|
}
|
|
}
|
|
|
|
|
|
func (p *Pattern) match(path string) (bool, error) {
|
|
func (p *Pattern) match(path string) (bool, error) {
|
|
- if p.regexp == nil {
|
|
|
|
|
|
+ if p.matchType == unknownMatch {
|
|
if err := p.compile(); err != nil {
|
|
if err := p.compile(); err != nil {
|
|
return false, filepath.ErrBadPattern
|
|
return false, filepath.ErrBadPattern
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- b := p.regexp.MatchString(path)
|
|
|
|
|
|
+ switch p.matchType {
|
|
|
|
+ case exactMatch:
|
|
|
|
+ return path == p.cleanedPattern, nil
|
|
|
|
+ case prefixMatch:
|
|
|
|
+ // strip trailing **
|
|
|
|
+ return strings.HasPrefix(path, p.cleanedPattern[:len(p.cleanedPattern)-2]), nil
|
|
|
|
+ case suffixMatch:
|
|
|
|
+ // strip leading **
|
|
|
|
+ suffix := p.cleanedPattern[2:]
|
|
|
|
+ if strings.HasSuffix(path, suffix) {
|
|
|
|
+ return true, nil
|
|
|
|
+ }
|
|
|
|
+ // **/foo matches "foo"
|
|
|
|
+ return suffix[0] == os.PathSeparator && path == suffix[1:], nil
|
|
|
|
+ case regexpMatch:
|
|
|
|
+ return p.regexp.MatchString(path), nil
|
|
|
|
+ }
|
|
|
|
|
|
- return b, nil
|
|
|
|
|
|
+ return false, nil
|
|
}
|
|
}
|
|
|
|
|
|
func (p *Pattern) compile() error {
|
|
func (p *Pattern) compile() error {
|
|
@@ -326,7 +353,8 @@ func (p *Pattern) compile() error {
|
|
escSL += `\`
|
|
escSL += `\`
|
|
}
|
|
}
|
|
|
|
|
|
- for scan.Peek() != scanner.EOF {
|
|
|
|
|
|
+ p.matchType = exactMatch
|
|
|
|
+ for i := 0; scan.Peek() != scanner.EOF; i++ {
|
|
ch := scan.Next()
|
|
ch := scan.Next()
|
|
|
|
|
|
if ch == '*' {
|
|
if ch == '*' {
|
|
@@ -341,20 +369,32 @@ func (p *Pattern) compile() error {
|
|
|
|
|
|
if scan.Peek() == scanner.EOF {
|
|
if scan.Peek() == scanner.EOF {
|
|
// is "**EOF" - to align with .gitignore just accept all
|
|
// is "**EOF" - to align with .gitignore just accept all
|
|
- regStr += ".*"
|
|
|
|
|
|
+ if p.matchType == exactMatch {
|
|
|
|
+ p.matchType = prefixMatch
|
|
|
|
+ } else {
|
|
|
|
+ regStr += ".*"
|
|
|
|
+ p.matchType = regexpMatch
|
|
|
|
+ }
|
|
} else {
|
|
} else {
|
|
// is "**"
|
|
// is "**"
|
|
// Note that this allows for any # of /'s (even 0) because
|
|
// Note that this allows for any # of /'s (even 0) because
|
|
// the .* will eat everything, even /'s
|
|
// the .* will eat everything, even /'s
|
|
regStr += "(.*" + escSL + ")?"
|
|
regStr += "(.*" + escSL + ")?"
|
|
|
|
+ p.matchType = regexpMatch
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if i == 0 {
|
|
|
|
+ p.matchType = suffixMatch
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
// is "*" so map it to anything but "/"
|
|
// is "*" so map it to anything but "/"
|
|
regStr += "[^" + escSL + "]*"
|
|
regStr += "[^" + escSL + "]*"
|
|
|
|
+ p.matchType = regexpMatch
|
|
}
|
|
}
|
|
} else if ch == '?' {
|
|
} else if ch == '?' {
|
|
// "?" is any char except "/"
|
|
// "?" is any char except "/"
|
|
regStr += "[^" + escSL + "]"
|
|
regStr += "[^" + escSL + "]"
|
|
|
|
+ p.matchType = regexpMatch
|
|
} else if shouldEscape(ch) {
|
|
} else if shouldEscape(ch) {
|
|
// Escape some regexp special chars that have no meaning
|
|
// Escape some regexp special chars that have no meaning
|
|
// in golang's filepath.Match
|
|
// in golang's filepath.Match
|
|
@@ -371,14 +411,22 @@ func (p *Pattern) compile() error {
|
|
}
|
|
}
|
|
if scan.Peek() != scanner.EOF {
|
|
if scan.Peek() != scanner.EOF {
|
|
regStr += `\` + string(scan.Next())
|
|
regStr += `\` + string(scan.Next())
|
|
|
|
+ p.matchType = regexpMatch
|
|
} else {
|
|
} else {
|
|
regStr += `\`
|
|
regStr += `\`
|
|
}
|
|
}
|
|
|
|
+ } else if ch == '[' || ch == ']' {
|
|
|
|
+ regStr += string(ch)
|
|
|
|
+ p.matchType = regexpMatch
|
|
} else {
|
|
} else {
|
|
regStr += string(ch)
|
|
regStr += string(ch)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if p.matchType != regexpMatch {
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+
|
|
regStr += "$"
|
|
regStr += "$"
|
|
|
|
|
|
re, err := regexp.Compile(regStr)
|
|
re, err := regexp.Compile(regStr)
|
|
@@ -387,6 +435,7 @@ func (p *Pattern) compile() error {
|
|
}
|
|
}
|
|
|
|
|
|
p.regexp = re
|
|
p.regexp = re
|
|
|
|
+ p.matchType = regexpMatch
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|