check / check (push) Successful in 9m29s
An exclude pattern that did not compile was skipped with a warning during the backup, which then stored what the user meant to leave out. Config.Validate now refuses it, naming the setting and quoting the pattern, for example exclude[2]: "build[" is not a valid exclude pattern: ... The compile step moves to a new package, internal/exclude, as exclude.Compile; Validate and the scanner both call it. The scanner's skip-and-warn path is gone; a pattern that does not compile there now fails the scanner. Model: opus-5-5
77 lines
2.3 KiB
Go
77 lines
2.3 KiB
Go
// Package exclude compiles exclude patterns into globs.
|
|
package exclude
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/gobwas/glob"
|
|
)
|
|
|
|
var errBadExcludePattern = errors.New("is not a valid exclude pattern")
|
|
|
|
// Compile compiles one exclude pattern into the globs that match it.
|
|
// anchored reports whether the pattern matches only from the root of the
|
|
// source directory. An empty pattern yields no globs. Config.Validate and
|
|
// the scanner both compile patterns with it, so a pattern that loads is one
|
|
// the scanner can use.
|
|
func Compile(pattern string) ([]glob.Glob, bool, error) {
|
|
if pattern == "" {
|
|
return nil, false, nil
|
|
}
|
|
|
|
// A leading "/" anchors the pattern to the source directory.
|
|
trimmed, anchored := strings.CutPrefix(pattern, "/")
|
|
|
|
// A trailing "/" marks a directory and is not matched.
|
|
trimmed = strings.TrimSuffix(trimmed, "/")
|
|
|
|
// A leading "**/" demands a "/" before the name and so misses an
|
|
// entry directly under the source path. It means "at every depth",
|
|
// which is what an unanchored pattern already does, so drop the
|
|
// prefix and match unanchored: as in gitignore, "**/x" and
|
|
// "/**/x" then also match a top-level "x".
|
|
if rest, found := strings.CutPrefix(trimmed, "**/"); found {
|
|
trimmed = rest
|
|
anchored = false
|
|
}
|
|
|
|
// Likewise "/**/" inside a pattern demands two slashes, so
|
|
// "a/**/*.log" misses "a/x.log". As in gitignore it stands for
|
|
// zero or more directories, so also compile the pattern with it
|
|
// made a single "/". The glob library's {a,b} is no help: with a
|
|
// "**" inside it in mid-pattern, it misses paths it should match.
|
|
var globs []glob.Glob
|
|
|
|
for _, variant := range doubleStarVariants(trimmed) {
|
|
g, err := glob.Compile(variant, '/')
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("%w: %w", errBadExcludePattern, err)
|
|
}
|
|
|
|
globs = append(globs, g)
|
|
}
|
|
|
|
return globs, anchored, nil
|
|
}
|
|
|
|
// doubleStarVariants returns pattern once for every way of keeping each
|
|
// "/**/" in it or making it a single "/".
|
|
func doubleStarVariants(pattern string) []string {
|
|
const doubleStar = "/**/"
|
|
|
|
before, after, found := strings.Cut(pattern, doubleStar)
|
|
if !found {
|
|
return []string{pattern}
|
|
}
|
|
|
|
var variants []string
|
|
|
|
for _, rest := range doubleStarVariants(after) {
|
|
variants = append(variants, before+doubleStar+rest, before+"/"+rest)
|
|
}
|
|
|
|
return variants
|
|
}
|