Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A Go library for matching paths against gitignore rules. Pattern matching uses a
- 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`
- Fast rejection for literal names and suffix patterns like `*.log`

```go
import "github.com/git-pkgs/gitignore"
Expand Down Expand Up @@ -111,7 +111,17 @@ A Matcher is safe for concurrent `Match`/`MatchPath`/`MatchDetail` calls once co

## Match semantics

Paths should use forward slashes and be relative to the repository root. Last-match-wins, same as git.
Paths should use forward slashes and be relative to the repository root. An ignored parent directory excludes all its descendants; otherwise, the last matching rule for the path wins.

## Benchmarks

Run benchmarks on an otherwise idle machine, using the same Go toolchain for both revisions. Record repeated samples and memory allocations:

```sh
go test -run '^$' -bench . -benchmem -count=10 -cpu=1
```

`BenchmarkCompile` and `BenchmarkWalkTree` include filesystem reads and a Git subprocess through `New`; `BenchmarkAddPatterns` measures parsing without filesystem access. Compare samples with `benchstat` and check the timing spread before quoting speed changes.

## License

Expand Down
44 changes: 44 additions & 0 deletions conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,50 @@ func TestConformanceGitStartupFailure(t *testing.T) {
}
}

func TestConformanceNestedWalk(t *testing.T) {
requireGit(t)
isolateGitEnv(t)
paths := parsePathList("a/cache/x\na/keep.log\na/drop.log\na/sub/keep.log\na/sub/drop.log\nb/drop.log\nb/keep.log\nb/cache/x\nblocked/sub/file\n")
root := buildRepo(t, "*.log\nblocked/\n", paths)
for dir, patterns := range map[string]string{
"a": "!keep.log\ncache/\n", "a/sub": "!drop.log\n",
"b": "!drop.log\n", "blocked/sub": "!file\n",
} {
if err := os.WriteFile(filepath.Join(root, dir, ".gitignore"), []byte(patterns), 0o644); err != nil {
t.Fatal(err)
}
}
want := gitCheckIgnore(t, root, paths)
m := gitignore.NewFromDirectory(root)
for _, p := range paths {
if got := m.Match(p.query()); got != want[p.rel] {
t.Errorf("NewFromDirectory Match(%q) = %v, git = %v", p.rel, got, want[p.rel])
}
}
for _, start := range []string{"", "a", "a/sub", "b", "blocked/sub"} {
visited := make(map[string]bool)
visit := func(path string, _ os.DirEntry) error {
visited[filepath.ToSlash(path)] = true
return nil
}
var err error
if start == "" {
err = gitignore.Walk(root, visit)
} else {
err = gitignore.WalkFrom(root, start, visit)
}
if err != nil {
t.Fatal(err)
}
for _, p := range paths {
inScope := start == "" || strings.HasPrefix(p.rel, start+"/")
if expected := inScope && !want[p.rel]; visited[p.rel] != expected {
t.Errorf("walk from %q: visited %q = %v, want %v", start, p.rel, visited[p.rel], expected)
}
}
}
}

// TestConformanceFuzz generates random pattern sets and paths, then compares
// the library against git check-ignore. It is skipped under -short.
func TestConformanceFuzz(t *testing.T) {
Expand Down
Loading