|
@@ -32,6 +32,14 @@ const (
|
|
resultFoldCase = 1 << iota
|
|
resultFoldCase = 1 << iota
|
|
)
|
|
)
|
|
|
|
|
|
|
|
+var defaultResult Result = resultInclude
|
|
|
|
+
|
|
|
|
+func init() {
|
|
|
|
+ if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
|
|
|
|
+ defaultResult |= resultFoldCase
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
type Pattern struct {
|
|
type Pattern struct {
|
|
pattern string
|
|
pattern string
|
|
match glob.Glob
|
|
match glob.Glob
|
|
@@ -52,6 +60,25 @@ func (p Pattern) String() string {
|
|
return ret
|
|
return ret
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (p Pattern) allowsSkippingIgnoredDirs() bool {
|
|
|
|
+ if p.result.IsIgnored() {
|
|
|
|
+ return true
|
|
|
|
+ }
|
|
|
|
+ if p.pattern[0] != '/' {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ // Double asterisk everywhere in the path except at the end is bad
|
|
|
|
+ if strings.Contains(strings.TrimSuffix(p.pattern, "**"), "**") {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ // Any wildcards anywhere except for the last path component are bad
|
|
|
|
+ lastSep := strings.LastIndex(p.pattern, "/")
|
|
|
|
+ if lastSep == -1 {
|
|
|
|
+ return true
|
|
|
|
+ }
|
|
|
|
+ return p.pattern[:lastSep] == glob.QuoteMeta(p.pattern[:lastSep])
|
|
|
|
+}
|
|
|
|
+
|
|
type Result uint8
|
|
type Result uint8
|
|
|
|
|
|
func (r Result) IsIgnored() bool {
|
|
func (r Result) IsIgnored() bool {
|
|
@@ -174,11 +201,20 @@ func (m *Matcher) parseLocked(r io.Reader, file string) error {
|
|
}
|
|
}
|
|
|
|
|
|
m.skipIgnoredDirs = true
|
|
m.skipIgnoredDirs = true
|
|
|
|
+ var previous string
|
|
for _, p := range patterns {
|
|
for _, p := range patterns {
|
|
- if !p.result.IsIgnored() {
|
|
|
|
|
|
+ // We automatically add patterns with a /** suffix, which normally
|
|
|
|
+ // means that we cannot skip directories. However if the same
|
|
|
|
+ // pattern without the /** already exists (which is true for
|
|
|
|
+ // automatically added patterns) we can skip.
|
|
|
|
+ if l := len(p.pattern); l > 3 && p.pattern[:len(p.pattern)-3] == previous {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ if !p.allowsSkippingIgnoredDirs() {
|
|
m.skipIgnoredDirs = false
|
|
m.skipIgnoredDirs = false
|
|
break
|
|
break
|
|
}
|
|
}
|
|
|
|
+ previous = p.pattern
|
|
}
|
|
}
|
|
|
|
|
|
m.curHash = newHash
|
|
m.curHash = newHash
|
|
@@ -360,87 +396,90 @@ func loadParseIncludeFile(filesystem fs.Filesystem, file string, cd ChangeDetect
|
|
return patterns, err
|
|
return patterns, err
|
|
}
|
|
}
|
|
|
|
|
|
-func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd ChangeDetector, linesSeen map[string]struct{}) ([]string, []Pattern, error) {
|
|
|
|
- var lines []string
|
|
|
|
- var patterns []Pattern
|
|
|
|
-
|
|
|
|
- defaultResult := resultInclude
|
|
|
|
- if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
|
|
|
|
- defaultResult |= resultFoldCase
|
|
|
|
|
|
+func parseLine(line string) ([]Pattern, error) {
|
|
|
|
+ pattern := Pattern{
|
|
|
|
+ result: defaultResult,
|
|
}
|
|
}
|
|
|
|
|
|
- addPattern := func(line string) error {
|
|
|
|
- pattern := Pattern{
|
|
|
|
- result: defaultResult,
|
|
|
|
- }
|
|
|
|
|
|
+ // Allow prefixes to be specified in any order, but only once.
|
|
|
|
+ var seenPrefix [3]bool
|
|
|
|
|
|
- // Allow prefixes to be specified in any order, but only once.
|
|
|
|
- var seenPrefix [3]bool
|
|
|
|
-
|
|
|
|
- for {
|
|
|
|
- if strings.HasPrefix(line, "!") && !seenPrefix[0] {
|
|
|
|
- seenPrefix[0] = true
|
|
|
|
- line = line[1:]
|
|
|
|
- pattern.result ^= resultInclude
|
|
|
|
- } else if strings.HasPrefix(line, "(?i)") && !seenPrefix[1] {
|
|
|
|
- seenPrefix[1] = true
|
|
|
|
- pattern.result |= resultFoldCase
|
|
|
|
- line = line[4:]
|
|
|
|
- } else if strings.HasPrefix(line, "(?d)") && !seenPrefix[2] {
|
|
|
|
- seenPrefix[2] = true
|
|
|
|
- pattern.result |= resultDeletable
|
|
|
|
- line = line[4:]
|
|
|
|
- } else {
|
|
|
|
- break
|
|
|
|
- }
|
|
|
|
|
|
+ for {
|
|
|
|
+ if strings.HasPrefix(line, "!") && !seenPrefix[0] {
|
|
|
|
+ seenPrefix[0] = true
|
|
|
|
+ line = line[1:]
|
|
|
|
+ pattern.result ^= resultInclude
|
|
|
|
+ } else if strings.HasPrefix(line, "(?i)") && !seenPrefix[1] {
|
|
|
|
+ seenPrefix[1] = true
|
|
|
|
+ pattern.result |= resultFoldCase
|
|
|
|
+ line = line[4:]
|
|
|
|
+ } else if strings.HasPrefix(line, "(?d)") && !seenPrefix[2] {
|
|
|
|
+ seenPrefix[2] = true
|
|
|
|
+ pattern.result |= resultDeletable
|
|
|
|
+ line = line[4:]
|
|
|
|
+ } else {
|
|
|
|
+ break
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- if pattern.result.IsCaseFolded() {
|
|
|
|
- line = strings.ToLower(line)
|
|
|
|
|
|
+ if pattern.result.IsCaseFolded() {
|
|
|
|
+ line = strings.ToLower(line)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pattern.pattern = line
|
|
|
|
+
|
|
|
|
+ var err error
|
|
|
|
+ if strings.HasPrefix(line, "/") {
|
|
|
|
+ // Pattern is rooted in the current dir only
|
|
|
|
+ pattern.match, err = glob.Compile(line[1:], '/')
|
|
|
|
+ return []Pattern{pattern}, err
|
|
|
|
+ }
|
|
|
|
+ patterns := make([]Pattern, 2)
|
|
|
|
+ if strings.HasPrefix(line, "**/") {
|
|
|
|
+ // Add the pattern as is, and without **/ so it matches in current dir
|
|
|
|
+ pattern.match, err = glob.Compile(line, '/')
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
}
|
|
}
|
|
|
|
+ patterns[0] = pattern
|
|
|
|
|
|
|
|
+ line = line[3:]
|
|
pattern.pattern = line
|
|
pattern.pattern = line
|
|
|
|
+ pattern.match, err = glob.Compile(line, '/')
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ patterns[1] = pattern
|
|
|
|
+ return patterns, nil
|
|
|
|
+ }
|
|
|
|
+ // Path name or pattern, add it so it matches files both in
|
|
|
|
+ // current directory and subdirs.
|
|
|
|
+ pattern.match, err = glob.Compile(line, '/')
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ patterns[0] = pattern
|
|
|
|
|
|
- var err error
|
|
|
|
- if strings.HasPrefix(line, "/") {
|
|
|
|
- // Pattern is rooted in the current dir only
|
|
|
|
- pattern.match, err = glob.Compile(line[1:], '/')
|
|
|
|
- if err != nil {
|
|
|
|
- return errors.Wrapf(err, "invalid pattern %q in ignore file", line)
|
|
|
|
- }
|
|
|
|
- patterns = append(patterns, pattern)
|
|
|
|
- } else if strings.HasPrefix(line, "**/") {
|
|
|
|
- // Add the pattern as is, and without **/ so it matches in current dir
|
|
|
|
- pattern.match, err = glob.Compile(line, '/')
|
|
|
|
- if err != nil {
|
|
|
|
- return errors.Wrapf(err, "invalid pattern %q in ignore file", line)
|
|
|
|
- }
|
|
|
|
- patterns = append(patterns, pattern)
|
|
|
|
|
|
+ line = "**/" + line
|
|
|
|
+ pattern.pattern = line
|
|
|
|
+ pattern.match, err = glob.Compile(line, '/')
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ patterns[1] = pattern
|
|
|
|
+ return patterns, nil
|
|
|
|
+}
|
|
|
|
|
|
- line = line[3:]
|
|
|
|
- pattern.pattern = line
|
|
|
|
- pattern.match, err = glob.Compile(line, '/')
|
|
|
|
- if err != nil {
|
|
|
|
- return errors.Wrapf(err, "invalid pattern %q in ignore file", line)
|
|
|
|
- }
|
|
|
|
- patterns = append(patterns, pattern)
|
|
|
|
- } else {
|
|
|
|
- // Path name or pattern, add it so it matches files both in
|
|
|
|
- // current directory and subdirs.
|
|
|
|
- pattern.match, err = glob.Compile(line, '/')
|
|
|
|
- if err != nil {
|
|
|
|
- return errors.Wrapf(err, "invalid pattern %q in ignore file", line)
|
|
|
|
- }
|
|
|
|
- patterns = append(patterns, pattern)
|
|
|
|
|
|
+func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd ChangeDetector, linesSeen map[string]struct{}) ([]string, []Pattern, error) {
|
|
|
|
+ var lines []string
|
|
|
|
+ var patterns []Pattern
|
|
|
|
|
|
- line := "**/" + line
|
|
|
|
- pattern.pattern = line
|
|
|
|
- pattern.match, err = glob.Compile(line, '/')
|
|
|
|
- if err != nil {
|
|
|
|
- return errors.Wrapf(err, "invalid pattern %q in ignore file", line)
|
|
|
|
- }
|
|
|
|
- patterns = append(patterns, pattern)
|
|
|
|
|
|
+ addPattern := func(line string) error {
|
|
|
|
+ newPatterns, err := parseLine(line)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return errors.Wrapf(err, "invalid pattern %q in ignore file", line)
|
|
}
|
|
}
|
|
|
|
+ patterns = append(patterns, newPatterns...)
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|