Refuse an exclude pattern that does not compile at config load (closes #455)
check / check (push) Successful in 9m29s
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
This commit is contained in:
1 parent
6a8bb2f389
commit
59d7376c04
5 files changed
+220
-69
No files matched your search
@@ -20,6 +20,7 @@ import (
|
||||
"sneak.berlin/go/vaultik/internal/blob"
|
||||
"sneak.berlin/go/vaultik/internal/chunker"
|
||||
"sneak.berlin/go/vaultik/internal/database"
|
||||
"sneak.berlin/go/vaultik/internal/exclude"
|
||||
"sneak.berlin/go/vaultik/internal/log"
|
||||
"sneak.berlin/go/vaultik/internal/storage"
|
||||
"sneak.berlin/go/vaultik/internal/types"
|
||||
@@ -221,8 +222,12 @@ func NewScanner(cfg ScannerConfig) *Scanner {
|
||||
progress = NewProgressReporter()
|
||||
}
|
||||
|
||||
// Compile exclude patterns
|
||||
compiledExclude := compileExcludePatterns(cfg.Exclude)
|
||||
compiledExclude, err := compileExcludePatterns(cfg.Exclude)
|
||||
if err != nil {
|
||||
log.Error("Failed to compile exclude patterns", "error", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
uiw := cfg.UI
|
||||
if uiw == nil {
|
||||
@@ -2318,51 +2323,18 @@ func wrapPermissionError(path string, err error) error {
|
||||
"with --skip-errors to continue past unreadable files)", path, err)
|
||||
}
|
||||
|
||||
// compileExcludePatterns compiles the exclude patterns into glob matchers
|
||||
func compileExcludePatterns(patterns []string) []compiledPattern {
|
||||
// compileExcludePatterns compiles the exclude patterns into glob matchers.
|
||||
// Config.Validate has already refused a pattern that does not compile.
|
||||
func compileExcludePatterns(patterns []string) ([]compiledPattern, error) {
|
||||
var compiled []compiledPattern
|
||||
|
||||
for _, p := range patterns {
|
||||
if p == "" {
|
||||
continue
|
||||
globs, anchored, err := exclude.Compile(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%q %w", p, err)
|
||||
}
|
||||
|
||||
// Check if pattern is anchored (starts with /)
|
||||
anchored := strings.HasPrefix(p, "/")
|
||||
|
||||
pattern := p
|
||||
if anchored {
|
||||
pattern = p[1:] // Remove leading /
|
||||
}
|
||||
|
||||
// Remove trailing slash if present (directory indicator)
|
||||
pattern = strings.TrimSuffix(pattern, "/")
|
||||
|
||||
// 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(pattern, "**/"); found {
|
||||
pattern = 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.
|
||||
for _, variant := range doubleStarVariants(pattern) {
|
||||
// For patterns without path separators, we need to match them as components
|
||||
// e.g., ".git" should match ".git" anywhere in the path
|
||||
g, err := glob.Compile(variant, '/')
|
||||
if err != nil {
|
||||
log.Warn("Invalid exclude pattern, skipping", "pattern", p, "error", err)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
for _, g := range globs {
|
||||
compiled = append(compiled, compiledPattern{
|
||||
pattern: g,
|
||||
anchored: anchored,
|
||||
@@ -2371,26 +2343,7 @@ func compileExcludePatterns(patterns []string) []compiledPattern {
|
||||
}
|
||||
}
|
||||
|
||||
return compiled
|
||||
}
|
||||
|
||||
// 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
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
// shouldExclude checks if a path should be excluded based on exclude patterns
|
||||
|
||||
Reference in new issue
Block a user