A Go library for matching paths against gitignore rules. Pattern matching uses a direct wildmatch implementation (two-pointer backtracking, same algorithm as git's wildmatch.c) rather than compiling patterns to regexes or delegating to filepath.Match
- Wildmatch engine modeled on git's own
wildmatch.c, tested against git's wildmatch test suite - Bracket expressions with ranges, negation, backslash escapes, and all 12 POSIX character classes (
[:alnum:],[:alpha:], etc.) - Proper
**handling (zero or more directories, only when standalone between separators) core.excludesfilesupport with XDG fallback- Automatic nested
.gitignorediscovery viaNewFromDirectoryandWalk - Negation patterns with correct last-match-wins semantics
- Directory-only patterns (trailing
/) with descendant matching - Match provenance via
MatchDetail(which pattern, file, and line number matched) - Invalid pattern surfacing via
Errors() - Literal suffix fast-reject for common patterns like
*.log
import "github.com/git-pkgs/gitignore"New reads the user's global excludes file, .git/info/exclude, and the root .gitignore:
m := gitignore.New("/path/to/repo")
m.Match("vendor/lib.go") // true if matched
m.Match("vendor/") // trailing slash tests as directoryFor repos with nested .gitignore files, NewFromDirectory walks the tree and loads them all, scoped to their containing directory:
m := gitignore.NewFromDirectory("/path/to/repo")To create a matcher with only programmatic patterns (no filesystem loading), pass an empty root:
m := gitignore.New("")
m.AddPatterns([]byte("*.tmp\n"), "")You can also add patterns to any matcher manually:
m.AddFromFile("/path/to/repo/src/.gitignore", "src")
m.AddPatterns([]byte("*.log\nbuild/\n"), "")Match uses the trailing-slash convention to distinguish files from directories. If you already know whether the path is a directory, MatchPath avoids that:
m.Match("vendor/") // directory
m.MatchPath("vendor", true) // same thing, no trailing slash neededTo find out which pattern matched (useful for debugging), use MatchDetail:
r := m.MatchDetail("app.log")
if r.Matched {
fmt.Printf("ignored by %s (line %d of %s)\n", r.Pattern, r.Line, r.Source)
}Walk traverses the repo, loading .gitignore files as it descends and skipping ignored entries. It never descends into .git or ignored directories.
gitignore.Walk("/path/to/repo", func(path string, d fs.DirEntry) error {
fmt.Println(path)
return nil
})WalkFrom walks a subdirectory while still respecting root-level rules. It loads global excludes, .git/info/exclude, the root .gitignore, and every .gitignore between the root and the start directory before walking, so patterns scoped above the start still apply. Paths passed to fn are relative to the root.
gitignore.WalkFrom("/path/to/repo", "src/pkg", func(path string, d fs.DirEntry) error {
fmt.Println(path) // e.g. "src/pkg/lib.go"
return nil
})Invalid patterns (like unknown POSIX character classes) are silently skipped during matching. To inspect them:
for _, err := range m.Errors() {
fmt.Println(err) // includes source file, line number, and reason
}A Matcher is safe for concurrent Match/MatchPath/MatchDetail calls once construction is complete. Don't call AddPatterns or AddFromFile concurrently with matching.
Paths should use forward slashes and be relative to the repository root. Last-match-wins, same as git.
MIT