@@ -9,11 +9,12 @@ import (
11 "github.com/charlievieth/fastwalk"
11 "github.com/charlievieth/fastwalk"
12+ "github.com/charmbracelet/crush/internal/csync"
12 ignore "github.com/sabhiram/go-gitignore"
13 ignore "github.com/sabhiram/go-gitignore"
15−// CommonIgnorePatterns contains commonly ignored files and directories
16+// commonIgnorePatterns contains commonly ignored files and directories
16−var CommonIgnorePatterns = []string{
17+var commonIgnorePatterns = ignore.CompileIgnoreLines(
17 // Version control
18 // Version control
@@ -68,62 +69,74 @@ var CommonIgnorePatterns = []string{
73−type DirectoryLister struct {
74+type directoryLister struct {
74− ignores *ignore.GitIgnore
75+ ignores *csync.Map[string, ignore.IgnoreParser]
75 rootPath string
76 rootPath string
78−func NewDirectoryLister(rootPath string) *DirectoryLister {
79+func NewDirectoryLister(rootPath string) *directoryLister {
79− dl := &DirectoryLister{
80+ return &directoryLister{
80 rootPath: rootPath,
81 rootPath: rootPath,
82+ ignores: csync.NewMap[string, ignore.IgnoreParser](),
83− dl.ignores = ignore.CompileIgnoreLines(append(CommonIgnorePatterns, strings.Split(parseIgnores(rootPath), "\n")...)...)
88−func parseIgnores(path string) string {
90− for _, ign := range []string{".crushignore", ".gitignore"} {
91− p := filepath.Join(path, ign)
92− if _, err := os.Stat(p); err == nil {
96− slog.Error("Failed to open ignore file", "path", p, "error", err)
99− if _, err := io.Copy(&b, f); err != nil {
100− slog.Error("Failed to read ignore file", "path", p, "error", err)
108−func (dl *DirectoryLister) shouldIgnore(path string, ignorePatterns []string) bool {
86+func (dl *directoryLister) shouldIgnore(path string, ignorePatterns []string) bool {
109 relPath, err := filepath.Rel(dl.rootPath, path)
87 relPath, err := filepath.Rel(dl.rootPath, path)
110 if err != nil {
88 if err != nil {
111 relPath = path
89 relPath = path
114− if dl.ignores.MatchesPath(relPath) {
118 base := filepath.Base(path)
92 base := filepath.Base(path)
120 for _, pattern := range ignorePatterns {
93 for _, pattern := range ignorePatterns {
121 matched, err := filepath.Match(pattern, base)
94 matched, err := filepath.Match(pattern, base)
122 if err == nil && matched {
95 if err == nil && matched {
96+ slog.Info("ignoring path", "path", path)
123 return true
97 return true
101+ if commonIgnorePatterns.MatchesPath(relPath) || dl.getIgnore(path).MatchesPath(relPath) {
102+ slog.Info("ignoring path", "path", path)
106+ parent := filepath.Dir(path)
108+ if dl.getIgnore(parent).MatchesPath(path) {
109+ slog.Info("ignoring path", "path", path, "parent", parent)
112+ if parent == "/" || parent == "." { // TODO: windows
126− return false
113+ return false
115+ parent = filepath.Dir(parent)
119+func (dl *directoryLister) getIgnore(path string) ignore.IgnoreParser {
120+ return dl.ignores.GetOrSet(path, func() ignore.IgnoreParser {
122+ for _, ign := range []string{".crushignore", ".gitignore"} {
123+ p := filepath.Join(path, ign)
124+ if _, err := os.Stat(p); err == nil {
125+ slog.Info("loading ignore file", "path", p)
126+ f, err := os.Open(p)
129+ slog.Error("Failed to open ignore file", "path", p, "error", err)
132+ if _, err := io.Copy(&b, f); err != nil {
133+ slog.Error("Failed to read ignore file", "path", p, "error", err)
138+ return ignore.CompileIgnoreLines(strings.Split(b.String(), "\n")...)
129// ListDirectory lists files and directories in the specified path,
142// ListDirectory lists files and directories in the specified path,