From 0fd28c286698f45b8e42dff737af4787107f97b8 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Fri, 3 Apr 2026 08:33:55 +0100 Subject: [PATCH] Skip filesystem pattern loading when root is empty New("") previously resolved "" as CWD and loaded .gitignore, .git/info/exclude, and global excludes, silently contaminating matchers intended for programmatic-only use. Fixes #7 --- README.md | 9 ++++++++- gitignore.go | 8 +++++++- gitignore_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8143997..2fb8bab 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,14 @@ For repos with nested `.gitignore` files, `NewFromDirectory` walks the tree and m := gitignore.NewFromDirectory("/path/to/repo") ``` -You can also add patterns manually: +To create a matcher with only programmatic patterns (no filesystem loading), pass an empty root: + +```go +m := gitignore.New("") +m.AddPatterns([]byte("*.tmp\n"), "") +``` + +You can also add patterns to any matcher manually: ```go m.AddFromFile("/path/to/repo/src/.gitignore", "src") diff --git a/gitignore.go b/gitignore.go index 92dcf3d..1900aea 100644 --- a/gitignore.go +++ b/gitignore.go @@ -89,10 +89,16 @@ func (m *Matcher) Errors() []PatternError { // patterns override earlier ones. // // The root parameter should be the repository working directory -// (containing .git/). +// (containing .git/). If root is empty, no filesystem patterns are +// loaded and the returned Matcher is empty. Use AddPatterns or +// AddFromFile to add patterns programmatically. func New(root string) *Matcher { m := &Matcher{} + if root == "" { + return m + } + // Read global excludes (lowest priority) if gef := globalExcludesFile(); gef != "" { if data, err := os.ReadFile(gef); err == nil { diff --git a/gitignore_test.go b/gitignore_test.go index 89ef219..446a540 100644 --- a/gitignore_test.go +++ b/gitignore_test.go @@ -2490,3 +2490,36 @@ func TestAddPatterns(t *testing.T) { } } } + +func TestNewEmptyRootSkipsFilesystem(t *testing.T) { + // Create a temporary directory with a .gitignore that excludes *.exe + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.exe\n"), 0644); err != nil { + t.Fatal(err) + } + + // Change to that directory so CWD has a .gitignore + orig, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + if err := os.Chdir(dir); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.Chdir(orig); err != nil { + t.Logf("failed to restore working directory: %v", err) + } + }) + + // New("") should not load any filesystem patterns + m := gitignore.New("") + m.AddPatterns([]byte("*.tmp"), "") + + if m.MatchPath("test.exe", false) { + t.Error("New(\"\") should not load .gitignore from CWD, but *.exe matched") + } + if !m.MatchPath("test.tmp", false) { + t.Error("AddPatterns(*.tmp) should still work after New(\"\")") + } +}