Make **/ in an exclude pattern match zero directories (closes #338)
check / check (push) Waiting to run
check / check (push) Waiting to run
The glob library needs a slash on both sides of **, so the anchored pattern /**/x missed an x directly under the snapshot path, and a/**/*.log missed a/x.log. As in gitignore, a leading **/ now makes any pattern, anchored or not, match at every depth, and a /**/ inside a pattern also matches a single slash. Judgement call: the test for a/**/b uses a/**/*.log, because with a plain name after the ** the glob library already matched a/b. Model: opus-5-5
This commit is contained in:
@@ -578,3 +578,52 @@ func TestExcludePatterns_DoubleStarFileAtEveryDepth(t *testing.T) {
|
||||
require.Equal(t, 1, result.FilesScanned,
|
||||
"**/ file pattern should match at the top level and nested")
|
||||
}
|
||||
|
||||
func TestExcludePatterns_AnchoredDoubleStarAtEveryDepth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// As in gitignore, /**/ also matches directly under the source dir
|
||||
fs := setupAnchoredTestFS(t)
|
||||
|
||||
scanner, repos, cleanup := createTestScanner(t, fs, []string{"/**/projectname"})
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
createSnapshotRecord(ctx, t, repos, "test-snapshot")
|
||||
|
||||
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
|
||||
require.NoError(t, err)
|
||||
|
||||
// /**/projectname should exclude BOTH:
|
||||
// - /backup/projectname/file.txt
|
||||
// - /backup/otherproject/projectname/file.txt
|
||||
// Total files: 4, excluded: 2
|
||||
require.Equal(t, 2, result.FilesScanned,
|
||||
"/**/ pattern should match at the top level and nested")
|
||||
}
|
||||
|
||||
func TestExcludePatterns_DoubleStarInMiddleMatchesZeroDirectories(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// As in gitignore, a/**/b matches a/b as well as a/c/b. The last part
|
||||
// is a glob: with a plain name the glob library already matched a/b.
|
||||
fs := &afero.MemMapFs{}
|
||||
for _, path := range []string{"/backup/a/x.log", "/backup/a/c/y.log", "/backup/a/c/kept.txt"} {
|
||||
require.NoError(t, fs.MkdirAll(filepath.Dir(path), 0755))
|
||||
require.NoError(t, afero.WriteFile(fs, path, []byte(path), 0644))
|
||||
}
|
||||
|
||||
scanner, repos, cleanup := createTestScanner(t, fs, []string{"a/**/*.log"})
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
createSnapshotRecord(ctx, t, repos, "test-snapshot")
|
||||
|
||||
result, err := scanner.Scan(ctx, "/backup", "test-snapshot")
|
||||
require.NoError(t, err)
|
||||
|
||||
// a/**/*.log should exclude /backup/a/x.log and /backup/a/c/y.log
|
||||
// Total files: 3, excluded: 2
|
||||
require.Equal(t, 1, result.FilesScanned,
|
||||
"a/**/*.log should match a/x.log as well as a/c/y.log")
|
||||
}
|
||||
|
||||
@@ -2023,13 +2023,20 @@ func compileExcludePatterns(patterns []string) []compiledPattern {
|
||||
pattern = strings.TrimSuffix(pattern, "/")
|
||||
|
||||
// A leading "**/" demands a "/" before the name and so misses an
|
||||
// entry directly under the source path. An unanchored pattern
|
||||
// already matches at every depth, so drop the prefix: as in
|
||||
// gitignore, "**/x" then also matches a top-level "x".
|
||||
if !anchored {
|
||||
// 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 strings.HasPrefix(pattern, "**/") {
|
||||
pattern = strings.TrimPrefix(pattern, "**/")
|
||||
anchored = false
|
||||
}
|
||||
|
||||
// Likewise "/**/" inside a pattern demands two slashes, so
|
||||
// "a/**/b" misses "a/b". As in gitignore it stands for zero or
|
||||
// more directories, so let it match a single "/" too.
|
||||
pattern = strings.ReplaceAll(pattern, "/**/", "{/,/**/}")
|
||||
|
||||
// Compile the glob pattern
|
||||
// For patterns without path separators, we need to match them as components
|
||||
// e.g., ".git" should match ".git" anywhere in the path
|
||||
|
||||
Reference in New Issue
Block a user