diff --git a/pkg/fsx/fs.go b/pkg/fsx/fs.go index ed9a83c73..3dae9ca1b 100644 --- a/pkg/fsx/fs.go +++ b/pkg/fsx/fs.go @@ -127,6 +127,12 @@ var heavyDirs = map[string]bool{ ".vscode": true, } +// allowedDirs are hidden directories that should be traversed despite starting with a dot. +var allowedDirs = map[string]bool{ + ".github": true, + ".gitlab": true, +} + // WalkFiles walks the directory tree starting at root and returns a list of file paths. // It is bounded by MaxFiles (defaults to DefaultMaxFiles) and skips hidden directories // and known heavy directories like node_modules, vendor, etc. @@ -176,12 +182,15 @@ func WalkFiles(ctx context.Context, root string, opts WalkFilesOptions) ([]strin name := d.Name() - // Skip hidden files/directories (starting with .) + // Skip hidden files/directories (starting with .) except allowed dirs if strings.HasPrefix(name, ".") && name != "." { if d.IsDir() { - return fs.SkipDir + if !allowedDirs[name] { + return fs.SkipDir + } + } else { + return nil } - return nil } // Skip known heavy directories diff --git a/pkg/fsx/walk_test.go b/pkg/fsx/walk_test.go index d598c5f0d..0120cec4f 100644 --- a/pkg/fsx/walk_test.go +++ b/pkg/fsx/walk_test.go @@ -168,12 +168,16 @@ func TestWalkFiles_HiddenDirectories(t *testing.T) { // config // .cache/ // data + // .github/ + // workflows/ + // ci.yaml // src/ // main.go dirs := []string{ filepath.Join(tmpDir, ".git"), filepath.Join(tmpDir, ".cache"), + filepath.Join(tmpDir, ".github", "workflows"), filepath.Join(tmpDir, "src"), } for _, d := range dirs { @@ -181,28 +185,39 @@ func TestWalkFiles_HiddenDirectories(t *testing.T) { } files := map[string]string{ - filepath.Join(tmpDir, ".git", "config"): "[core]", - filepath.Join(tmpDir, ".cache", "data"): "cached", - filepath.Join(tmpDir, "src", "main.go"): "package main", + filepath.Join(tmpDir, ".git", "config"): "[core]", + filepath.Join(tmpDir, ".cache", "data"): "cached", + filepath.Join(tmpDir, ".github", "workflows", "ci.yaml"): "name: CI", + filepath.Join(tmpDir, "src", "main.go"): "package main", } for path, content := range files { require.NoError(t, os.WriteFile(path, []byte(content), 0o644)) } - t.Run("skips hidden directories", func(t *testing.T) { + t.Run("skips most hidden directories but allows .github", func(t *testing.T) { t.Parallel() got, err := WalkFiles(t.Context(), tmpDir, WalkFilesOptions{}) require.NoError(t, err) - assert.Len(t, got, 1, "should only find src/main.go") - assert.Contains(t, got[0], "main.go") + // Should find src/main.go and .github/workflows/ci.yaml + assert.Len(t, got, 2, "should find src/main.go and .github/workflows/ci.yaml") + // Verify .github files are included + var foundGithub, foundMain bool for _, f := range got { - assert.False(t, strings.HasPrefix(filepath.Base(f), ".")) - assert.NotContains(t, f, ".git") + if strings.Contains(f, ".github") { + foundGithub = true + } + if strings.Contains(f, "main.go") { + foundMain = true + } + // These should still be excluded + assert.NotContains(t, f, ".git"+string(filepath.Separator)) assert.NotContains(t, f, ".cache") } + assert.True(t, foundGithub, "should include .github files") + assert.True(t, foundMain, "should include src/main.go") }) } @@ -355,6 +370,58 @@ func TestWalkFiles_ContextCancellation(t *testing.T) { }) } +func TestWalkFiles_AllowedHiddenDirectories(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + + // Create .github and .gitlab (allowed) with files + allowedDirs := []string{".github", ".gitlab"} + for _, dir := range allowedDirs { + dirPath := filepath.Join(tmpDir, dir) + require.NoError(t, os.MkdirAll(dirPath, 0o755)) + filePath := filepath.Join(dirPath, "config.yaml") + require.NoError(t, os.WriteFile(filePath, []byte("content"), 0o644)) + } + + // Create other hidden directories that should be skipped + skippedDirs := []string{".hidden", ".circleci", ".husky", ".devcontainer"} + for _, dir := range skippedDirs { + dirPath := filepath.Join(tmpDir, dir) + require.NoError(t, os.MkdirAll(dirPath, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(dirPath, "config.yaml"), []byte("content"), 0o644)) + } + + t.Run("includes only .github and .gitlab hidden directories", func(t *testing.T) { + t.Parallel() + + got, err := WalkFiles(t.Context(), tmpDir, WalkFilesOptions{}) + require.NoError(t, err) + + // Should find files only in .github and .gitlab (2 files) + assert.Len(t, got, 2, "should find files only in .github and .gitlab") + + // Verify .github and .gitlab are included + for _, dir := range allowedDirs { + found := false + for _, f := range got { + if strings.HasPrefix(f, dir+string(filepath.Separator)) { + found = true + break + } + } + assert.True(t, found, "should include files from %s", dir) + } + + // Verify other hidden dirs are NOT included + for _, f := range got { + for _, dir := range skippedDirs { + assert.NotContains(t, f, dir, "should not include %s directory", dir) + } + } + }) +} + func TestWalkFiles_EmptyDirectory(t *testing.T) { t.Parallel()