From 3ff4bec535bb5bf54671ed829afa80605bb2bb3f Mon Sep 17 00:00:00 2001 From: Alexandre Rodrigues Date: Thu, 17 Sep 2026 13:19:14 -0300 Subject: [PATCH 1/2] fix(ignore): avoid mutating root patterns during matching Concurrent calls to `Rules.Ignore` race when a `.helmignore` pattern starts with `/`: each match writes the stripped pattern back into a captured variable. Strip the prefix while constructing the matcher, leaving evaluation read-only. A concurrent regression test checks that the root file matches and the same name in a subdirectory does not. Signed-off-by: Alexandre Rodrigues --- pkg/ignore/rules.go | 2 +- pkg/ignore/rules_test.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/ignore/rules.go b/pkg/ignore/rules.go index a8160da2a..17fcf6306 100644 --- a/pkg/ignore/rules.go +++ b/pkg/ignore/rules.go @@ -172,8 +172,8 @@ func (r *Rules) parseRule(rule string) error { if after, ok := strings.CutPrefix(rule, "/"); ok { // Require path matches the root path. + rule = after p.match = func(n string, _ os.FileInfo) bool { - rule = after ok, err := filepath.Match(rule, n) if err != nil { slog.Error("failed to compile", slog.String("rule", rule), slog.Any("error", err)) diff --git a/pkg/ignore/rules_test.go b/pkg/ignore/rules_test.go index 30e157a52..6b091f6da 100644 --- a/pkg/ignore/rules_test.go +++ b/pkg/ignore/rules_test.go @@ -20,6 +20,7 @@ import ( "bytes" "os" "path/filepath" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -133,3 +134,18 @@ func parseString(str string) (*Rules, error) { b := bytes.NewBuffer([]byte(str)) return Parse(b) } + +func TestIgnoreRootPatternConcurrent(t *testing.T) { + rules, err := parseString("/root.txt") + require.NoError(t, err) + var workers sync.WaitGroup + for range 8 { + workers.Go(func() { + for range 100 { + assert.True(t, rules.Ignore("root.txt", nil)) + assert.False(t, rules.Ignore("nested/root.txt", nil)) + } + }) + } + workers.Wait() +} From 391bbc502e0630498f9f7c7b9d7413a1bb42f155 Mon Sep 17 00:00:00 2001 From: Alexandre Rodrigues Date: Sat, 3 Oct 2026 01:09:08 -0300 Subject: [PATCH 2/2] test(ignore): run race regression check in CI Run the ignore package with the race detector in the build-test workflow. Document that the concurrent root-pattern test needs -race because matching assertions can pass despite concurrent writes. Signed-off-by: Alexandre Rodrigues --- .github/workflows/build-test.yml | 2 ++ pkg/ignore/rules_test.go | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 9503674ed..b2bf21776 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -32,5 +32,7 @@ jobs: run: go mod tidy -diff - name: Run unit tests run: make test-coverage + - name: Test ignore rules with the race detector + run: go test -race ./pkg/ignore - name: Test build run: make build diff --git a/pkg/ignore/rules_test.go b/pkg/ignore/rules_test.go index 6b091f6da..310daca43 100644 --- a/pkg/ignore/rules_test.go +++ b/pkg/ignore/rules_test.go @@ -136,6 +136,7 @@ func parseString(str string) (*Rules, error) { } func TestIgnoreRootPatternConcurrent(t *testing.T) { + // Run with -race to detect concurrent writes even when matching results agree. rules, err := parseString("/root.txt") require.NoError(t, err) var workers sync.WaitGroup