@@ -1,6 +1,7 @@
11package fsext
22
33import (
4+ "cmp"
45 "errors"
56 "log/slog"
67 "os"
@@ -12,6 +13,7 @@ import (
1213 "github.com/charlievieth/fastwalk"
1314 "github.com/charmbracelet/crush/internal/csync"
1415 "github.com/charmbracelet/crush/internal/home"
16+ gitconfig "github.com/go-git/go-git/v5/config"
1517 "github.com/go-git/go-git/v5/plumbing/format/gitignore"
1618)
1719
@@ -80,18 +82,52 @@ var commonIgnorePatterns = sync.OnceValue(func() []gitignore.Pattern {
8082 return parsePatterns(patterns, nil)
8183})
8284
83−var homeIgnorePatterns = sync.OnceValue(func() []gitignore.Pattern {
84− homeDir := home.Dir()
85− var lines []string
86− for _, name := range []string{
87− filepath.Join(homeDir, ".gitignore"),
88− filepath.Join(homeDir, ".config", "git", "ignore"),
89− filepath.Join(homeDir, ".config", "crush", "ignore"),
90− } {
91− if bts, err := os.ReadFile(name); err == nil {
92− lines = append(lines, strings.Split(string(bts), "\n")...)
85+// gitGlobalIgnorePatterns returns patterns from git's global excludes file
86+// (core.excludesFile), following git's config resolution order.
87+var gitGlobalIgnorePatterns = sync.OnceValue(func() []gitignore.Pattern {
88+ cfg, err := gitconfig.LoadConfig(gitconfig.GlobalScope)
89+ if err != nil {
90+ slog.Debug("Failed to load global git config", "error", err)
91+ return nil
92+ }
93+
94+ configPath := cmp.Or(
95+ os.Getenv("XDG_CONFIG_HOME"),
96+ filepath.Join(home.Dir(), ".config"),
97+ )
98+ excludesFilePath := cmp.Or(
99+ cfg.Raw.Section("core").Options.Get("excludesfile"),
100+ filepath.Join(configPath, "git", "ignore"),
101+ )
102+ excludesFilePath = home.Long(excludesFilePath)
103+
104+ bts, err := os.ReadFile(excludesFilePath)
105+ if err != nil {
106+ if !os.IsNotExist(err) {
107+ slog.Debug("Failed to read git global excludes file", "path", excludesFilePath, "error", err)
93108 }
109+ return nil
110+ }
111+
112+ return parsePatterns(strings.Split(string(bts), "\n"), nil)
113+})
114+
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"),
121+ )
122+ name := filepath.Join(configPath, "crush", "ignore")
123+ bts, err := os.ReadFile(name)
124+ if err != nil {
125+ if !os.IsNotExist(err) {
126+ slog.Debug("Failed to read crush global ignore file", "path", name, "error", err)
127+ }
128+ return nil
94129 }
130+ lines := strings.Split(string(bts), "\n")
95131 return parsePatterns(lines, nil)
96132})
97133
@@ -169,8 +205,9 @@ func (dl *directoryLister) getCombinedMatcher(dir string) gitignore.Matcher {
169205 // Add common patterns first (lowest priority).
170206 allPatterns = append(allPatterns, commonIgnorePatterns()...)
171207
172− // Add home ignore patterns.
173− allPatterns = append(allPatterns, homeIgnorePatterns()...)
208+ // Add global ignore patterns (git core.excludesFile + crush global ignore).
209+ allPatterns = append(allPatterns, gitGlobalIgnorePatterns()...)
210+ allPatterns = append(allPatterns, crushGlobalIgnorePatterns()...)
174211
175212 // Collect patterns from root to this directory.
176213 relDir, _ := filepath.Rel(dl.rootPath, dir)