@@ -1,6 +1,7 @@
1package fsext
1package fsext
@@ -12,6 +13,7 @@ import (
12 "github.com/charlievieth/fastwalk"
13 "github.com/charlievieth/fastwalk"
13 "github.com/charmbracelet/crush/internal/csync"
14 "github.com/charmbracelet/crush/internal/csync"
14 "github.com/charmbracelet/crush/internal/home"
15 "github.com/charmbracelet/crush/internal/home"
16+ gitconfig "github.com/go-git/go-git/v5/config"
15 "github.com/go-git/go-git/v5/plumbing/format/gitignore"
17 "github.com/go-git/go-git/v5/plumbing/format/gitignore"
@@ -80,18 +82,52 @@ var commonIgnorePatterns = sync.OnceValue(func() []gitignore.Pattern {
80 return parsePatterns(patterns, nil)
82 return parsePatterns(patterns, nil)
85+// gitGlobalIgnorePatterns returns patterns from git's global excludes file
86+// (core.excludesFile), following git's config resolution order.
83−var homeIgnorePatterns = sync.OnceValue(func() []gitignore.Pattern {
87+var gitGlobalIgnorePatterns = sync.OnceValue(func() []gitignore.Pattern {
84− homeDir := home.Dir()
88+ cfg, err := gitconfig.LoadConfig(gitconfig.GlobalScope)
85− var lines []string
89+ if err != nil {
86− for _, name := range []string{
90+ slog.Debug("Failed to load global git config", "error", err)
87− filepath.Join(homeDir, ".gitignore"),
91+ return nil
88− filepath.Join(homeDir, ".config", "git", "ignore"),
92+ }
94+ configPath := cmp.Or(
95+ os.Getenv("XDG_CONFIG_HOME"),
96+ filepath.Join(home.Dir(), ".config"),
98+ excludesFilePath := cmp.Or(
99+ cfg.Raw.Section("core").Options.Get("excludesfile"),
89− filepath.Join(homeDir, ".config", "crush", "ignore"),
100+ filepath.Join(configPath, "git", "ignore"),
102+ excludesFilePath = home.Long(excludesFilePath)
91− if bts, err := os.ReadFile(name); err == nil {
104+ bts, err := os.ReadFile(excludesFilePath)
92− lines = append(lines, strings.Split(string(bts), "\n")...)
105+ if err != nil {
106+ if !os.IsNotExist(err) {
107+ slog.Debug("Failed to read git global excludes file", "path", excludesFilePath, "error", err)
112+ return parsePatterns(strings.Split(string(bts), "\n"), nil)
115+// crushGlobalIgnorePatterns returns patterns from the user's
116+// ~/.config/crush/ignore file.
117+var crushGlobalIgnorePatterns = sync.OnceValue(func() []gitignore.Pattern {
118+ configPath := cmp.Or(
119+ os.Getenv("XDG_CONFIG_HOME"),
120+ filepath.Join(home.Dir(), ".config"),
122+ name := filepath.Join(configPath, "crush", "ignore")
123+ bts, err := os.ReadFile(name)
125+ if !os.IsNotExist(err) {
126+ slog.Debug("Failed to read crush global ignore file", "path", name, "error", err)
130+ lines := strings.Split(string(bts), "\n")
95 return parsePatterns(lines, nil)
131 return parsePatterns(lines, nil)
@@ -169,8 +205,9 @@ func (dl *directoryLister) getCombinedMatcher(dir string) gitignore.Matcher {
169 // Add common patterns first (lowest priority).
205 // Add common patterns first (lowest priority).
170 allPatterns = append(allPatterns, commonIgnorePatterns()...)
206 allPatterns = append(allPatterns, commonIgnorePatterns()...)
172− // Add home ignore patterns.
208+ // Add global ignore patterns (git core.excludesFile + crush global ignore).
209+ allPatterns = append(allPatterns, gitGlobalIgnorePatterns()...)
173− allPatterns = append(allPatterns, homeIgnorePatterns()...)
210+ allPatterns = append(allPatterns, crushGlobalIgnorePatterns()...)
175 // Collect patterns from root to this directory.
212 // Collect patterns from root to this directory.
176 relDir, _ := filepath.Rel(dl.rootPath, dir)
213 relDir, _ := filepath.Rel(dl.rootPath, dir)