feat: support .crushignore as well as .gitignore

Carlos Alexandro Becker created

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

Change summary

internal/fsext/fileutil.go    | 27 ++++++++++++++++++++--
internal/fsext/ignore_test.go | 44 +++++++++++++++++++++++++++++++++++++
internal/fsext/ls.go          | 14 +++++++++++
3 files changed, 82 insertions(+), 3 deletions(-)

Detailed changes

internal/fsext/fileutil.go 🔗

@@ -107,8 +107,9 @@ func SkipHidden(path string) bool {
 
 // FastGlobWalker provides gitignore-aware file walking with fastwalk
 type FastGlobWalker struct {
-	gitignore *ignore.GitIgnore
-	rootPath  string
+	gitignore   *ignore.GitIgnore
+	crushignore *ignore.GitIgnore
+	rootPath    string
 }
 
 func NewFastGlobWalker(searchPath string) *FastGlobWalker {
@@ -124,6 +125,14 @@ func NewFastGlobWalker(searchPath string) *FastGlobWalker {
 		}
 	}
 
+	// Load crushignore if it exists
+	crushignorePath := filepath.Join(searchPath, ".crushignore")
+	if _, err := os.Stat(crushignorePath); err == nil {
+		if ci, err := ignore.CompileIgnoreFile(crushignorePath); err == nil {
+			walker.crushignore = ci
+		}
+	}
+
 	return walker
 }
 
@@ -132,13 +141,25 @@ func (w *FastGlobWalker) shouldSkip(path string) bool {
 		return true
 	}
 
+	relPath, err := filepath.Rel(w.rootPath, path)
+	if err != nil {
+		relPath = path
+	}
+
+	// Check gitignore patterns if available
 	if w.gitignore != nil {
-		relPath, err := filepath.Rel(w.rootPath, path)
 		if err == nil && w.gitignore.MatchesPath(relPath) {
 			return true
 		}
 	}
 
+	// Check crushignore patterns if available
+	if w.crushignore != nil {
+		if err == nil && w.crushignore.MatchesPath(relPath) {
+			return true
+		}
+	}
+
 	return false
 }
 

internal/fsext/ignore_test.go 🔗

@@ -0,0 +1,44 @@
+package fsext
+
+import (
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestCrushIgnore(t *testing.T) {
+	// Create a temporary directory for testing
+	tempDir := t.TempDir()
+
+	// Change to temp directory
+	oldWd, _ := os.Getwd()
+	err := os.Chdir(tempDir)
+	require.NoError(t, err)
+	defer os.Chdir(oldWd)
+
+	// Create test files
+	require.NoError(t, os.WriteFile("test1.txt", []byte("test"), 0644))
+	require.NoError(t, os.WriteFile("test2.log", []byte("test"), 0644))
+	require.NoError(t, os.WriteFile("test3.tmp", []byte("test"), 0644))
+
+	// Create a .crushignore file that ignores .log files
+	require.NoError(t, os.WriteFile(".crushignore", []byte("*.log\n"), 0644))
+
+	// Test DirectoryLister
+	t.Run("DirectoryLister respects .crushignore", func(t *testing.T) {
+		dl := NewDirectoryLister(tempDir)
+
+		// Test that .log files are ignored
+		require.True(t, dl.gitignore == nil, "gitignore should be nil")
+		require.NotNil(t, dl.crushignore, "crushignore should not be nil")
+	})
+
+	// Test FastGlobWalker
+	t.Run("FastGlobWalker respects .crushignore", func(t *testing.T) {
+		walker := NewFastGlobWalker(tempDir)
+		
+		require.True(t, walker.gitignore == nil, "gitignore should be nil")
+		require.NotNil(t, walker.crushignore, "crushignore should not be nil")
+	})
+}

internal/fsext/ls.go 🔗

@@ -68,6 +68,7 @@ var CommonIgnorePatterns = []string{
 
 type DirectoryLister struct {
 	gitignore    *ignore.GitIgnore
+	crushignore  *ignore.GitIgnore
 	commonIgnore *ignore.GitIgnore
 	rootPath     string
 }
@@ -85,6 +86,14 @@ func NewDirectoryLister(rootPath string) *DirectoryLister {
 		}
 	}
 
+	// Load crushignore if it exists
+	crushignorePath := filepath.Join(rootPath, ".crushignore")
+	if _, err := os.Stat(crushignorePath); err == nil {
+		if ci, err := ignore.CompileIgnoreFile(crushignorePath); err == nil {
+			dl.crushignore = ci
+		}
+	}
+
 	// Create common ignore patterns
 	dl.commonIgnore = ignore.CompileIgnoreLines(CommonIgnorePatterns...)
 
@@ -107,6 +116,11 @@ func (dl *DirectoryLister) shouldIgnore(path string, ignorePatterns []string) bo
 		return true
 	}
 
+	// Check crushignore patterns if available
+	if dl.crushignore != nil && dl.crushignore.MatchesPath(relPath) {
+		return true
+	}
+
 	base := filepath.Base(path)
 
 	for _, pattern := range ignorePatterns {