From 7490cecc11ddd91cbba8370df5c014e95b528b98 Mon Sep 17 00:00:00 2001 From: Scott Rigby Date: Sun, 14 Dec 2025 17:28:48 -0500 Subject: [PATCH 1/2] Bring .helmignore to parity with .gitignore for Charts v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /## Summary This HIP proposes bringing `.helmignore` file targeting semantics to full parity with `.gitignore` syntax and matching rules for Helm Charts v3. /## Problem Users expect `.helmignore` to work like `.gitignore`, but the current implementation has critical divergences: - Broken negation: Whitelist patterns like `/*` + `!Chart.yaml` fail because negation logic is inverted (helm#3622, helm#8688) - No `**` globs: Recursive patterns are explicitly unsupported - First-match semantics: Helm stops at first match; Git uses last-match-wins These issues have persisted for years (PR #12265 stalled since 2023) because fixing them is a breaking change. /## Solution Scope the fix to Charts v3 (per HIP-0020), providing: - Full `.gitignore` pattern syntax (`**`, `!`, escape sequences) - Last-match-wins rule evaluation - Clean opt-in boundary—v2 charts unchanged, v3 charts get correct behavior /## References - Depends on: HIP-0020 (Charts v3 Enablement) - Implementation approach: Use go-git's gitignore (same as FluxCD's .sourceignore) Signed-off-by: Scott Rigby --- hips/hip-9999.md | 346 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 hips/hip-9999.md diff --git a/hips/hip-9999.md b/hips/hip-9999.md new file mode 100644 index 00000000..63820b11 --- /dev/null +++ b/hips/hip-9999.md @@ -0,0 +1,346 @@ +--- +hip: 9999 +title: "Bring .helmignore to parity with .gitignore file targeting syntax" +authors: ["Scott Rigby "] +created: "2025-12-14" +type: "feature" +status: "draft" +requires: ["HIP-0020"] +replaces: +superseded-by: +--- + +## Abstract + +This proposal brings `.helmignore` file targeting semantics to full parity with `.gitignore` syntax and matching rules for Helm Charts v3. The current `.helmignore` implementation diverges from `.gitignore` in critical ways—most notably in rule evaluation order (first-match vs. last-match), negation pattern behavior, and lack of `**` recursive glob support. These differences cause confusion and bugs for users who reasonably expect `.helmignore` to behave like `.gitignore`. By scoping this change to Charts v3 (per [HIP-0020][hip-0020]), existing charts continue to work unchanged while v3 charts opt into consistent, predictable ignore behavior. + +## Motivation + +Users familiar with `.gitignore` expect the same behavior from `.helmignore`. The current divergence causes: + +1. **Broken negation patterns**: Users cannot use whitelist-style patterns (e.g., `/*` then `!Chart.yaml`) because the current implementation has inverted negation semantics ([#8688], [#3622], [#1776]). + +2. **Missing recursive globs**: The `**` pattern is explicitly unsupported, forcing verbose workarounds for common cases like `**/test/` ([#12592]). + +3. **Unexpected first-match behavior**: Git uses "last matching rule wins"; Helm stops at the first match. This breaks patterns that progressively refine what to exclude. + +4. **Scope confusion**: Users expect `.helmignore` to only affect `helm package`, but it affects all commands including `template`, `install`, and `upgrade` ([#6075], [helm-www#1171]). + +5. **Documentation gaps**: Current docs don't clearly explain limitations or differences from `.gitignore` ([#4638], [helm-www#1312]). + +### Evidence of User Pain + +A comprehensive search of helm/helm issues reveals **27 directly related issues** and **5 pull requests**. Key themes: + +- **Pattern matching logic**: [#8688], [#3622], [#1776], [#12592] +- **Scope/behavior confusion**: [#6075], [#3050], [#10764] +- **Feature requests for global ignore**: [#1674], [#5675] +- **Documentation issues**: [#4638], [helm-www#1171], [helm-www#1312], [helm-www#1460] + +The recurring user expectation is clear: `.helmignore` should work like `.gitignore`. + +## Rationale + +### Why .gitignore Parity? + +1. **Reduced cognitive load**: Developers already know `.gitignore` semantics. Aligning `.helmignore` eliminates a class of surprises. + +2. **Well-documented spec**: Git's ignore format is [thoroughly documented][git-gitignore] and battle-tested across millions of repositories. + +3. **Ecosystem consistency**: Tools like Docker (`.dockerignore`), npm (`.npmignore`), and FluxCD (`.sourceignore`) all follow `.gitignore` semantics. + +### Why Charts v3? + +Per [HIP-0020][hip-0020], Charts v3 provides a clean opt-in boundary for breaking changes: + +1. **Breaking change isolation**: Charts explicitly declare `apiVersion: v3`, accepting new semantics. Charts v2 behavior is preserved unchanged. + +2. **No forced migration**: Existing charts continue to work. Users migrate on their own schedule. + +3. **Independent evolution**: Chart changes are decoupled from Helm version, allowing adequate time for testing and adoption. + +This is the appropriate scope because changing `.helmignore` semantics would break charts that depend on current (albeit buggy) behavior. + +## Specification + +### Behavioral Parity with .gitignore + +Charts v3 `.helmignore` files SHALL support the full `.gitignore` pattern format as documented at [git-scm.com/docs/gitignore][git-gitignore]: + +#### Pattern Types + +| Pattern | Behavior | +| ----------- | ------------------------------------------------ | +| Blank lines | Ignored (separators for readability) | +| `# comment` | Lines starting with `#` are comments | +| `\#` | Escaped `#` matches literal `#` filename | +| `!pattern` | Negation—re-includes previously excluded matches | +| `/pattern` | Leading `/` anchors pattern to chart root | +| `pattern/` | Trailing `/` matches directories only | +| `*` | Matches anything except `/` | +| `?` | Matches any single character except `/` | +| `[a-z]` | Character class matching | +| `**` | Recursive glob (see below) | +| `\ ` | Escaped trailing space (preserved) | +| `\!` | Escaped `!` matches literal `!` filename | + +#### Recursive Glob Semantics (`**`) + +- `**/foo` — matches `foo` in all directories +- `foo/**` — matches everything inside `foo/` +- `a/**/b` — matches `a/b`, `a/x/b`, `a/x/y/b`, etc. + +#### Rule Evaluation Order + +**Last matching rule wins**. All rules are evaluated; the final matching rule determines whether a path is included or excluded. This enables patterns like: + +``` +# Exclude everything +/* + +# But include these +!Chart.yaml +!values.yaml +!templates/ +``` + +#### Negation Behavior + +- `!pattern` re-includes files that match, reversing a prior exclusion +- **Limitation**: Cannot re-include a file if its parent directory was excluded (Git skips listing excluded directories for performance) + +#### Scope Clarification + +Helm has no concept of staging or committing files. This proposal addresses **file targeting syntax and semantics only**—specifically, which files are included/excluded during chart operations. Git's behavior around staged/committed files does not apply. + +### Technical Requirements + +1. **Parser/Matcher**: Implement a `.gitignore`-compatible lexer, parser, and matcher supporting all pattern types above. + +2. **Last-match semantics**: Evaluate all rules; final matching rule determines outcome. + +3. **Path normalization**: Normalize paths to forward slashes for cross-platform consistency. + +4. **Directory short-circuit**: Once a directory is excluded, skip traversing its contents (matches Git's performance optimization). + +5. **Self-exclusion**: `.helmignore` MAY be automatically excluded (unlike current behavior per [#9436]). + +### Integration Points + +Apply `.gitignore`-parity matching in all chart file operations: + +- `helm package` +- `helm lint` +- `helm template` +- `helm install` / `helm upgrade` (for local charts) +- `.Files.Get` / `.Files.Glob` (file access in templates) + +### Gating + +- **Charts v3 (`apiVersion: v3`)**: New `.gitignore`-parity semantics +- **Charts v2 (`apiVersion: v2`)**: Existing behavior unchanged + +## Backwards Compatibility + +### Charts v2 + +No behavior change. Existing `.helmignore` files continue to work exactly as before, preserving compatibility for all current charts. + +### Charts v3 + +Charts opting into v3 accept new `.helmignore` semantics. Potential breaking changes for charts migrating from v2: + +| Current Behavior | New Behavior | Migration Impact | +| ------------------------ | -------------------------- | ------------------------------------------------------------ | +| First-match evaluation | Last-match evaluation | Patterns relying on early termination may behave differently | +| Negation inverts match | Negation re-includes | Whitelist patterns will finally work correctly | +| `**` causes error | `**` works | No breakage (feature addition) | +| Trailing spaces stripped | Preserved with `\ ` | Unlikely to affect real charts | +| No escape sequences | `\#`, `\!`, `\ ` supported | No breakage (feature addition) | + +### Migration Guidance + +1. **Test with v3**: Before changing `apiVersion`, test charts with new semantics +2. **Review negation patterns**: Patterns with `!` may now work as originally intended +3. **Simplify patterns**: `**` globs can replace verbose directory-specific patterns + +## Security Implications + +None. This change only affects which local files are considered during chart operations. It does not: + +- Introduce new attack surfaces +- Add remote dependencies +- Change network behavior +- Affect chart signing or verification + +## How to Teach This + +### Documentation Updates + +1. **Primary statement**: "Charts v3 `.helmignore` uses identical pattern rules to `.gitignore`." + +2. **Link to Git documentation**: Reference [git-scm.com/docs/gitignore][git-gitignore] as the canonical spec. + +3. **Migration guide**: Document behavior differences between v2 and v3. + +4. **Examples**: Provide side-by-side comparisons showing: + - Whitelist patterns (`/*`, `!Chart.yaml`) + - Recursive globs (`**/test/`) + - Last-match ordering + +### Release Notes + +- Highlight UX improvement for users expecting `.gitignore` behavior +- Link to this HIP and HIP-0020 for context +- Emphasize opt-in nature via Charts v3 + +### Example Patterns + +```gitignore +# Comments start with # + +# Ignore all dotfiles +.* + +# But keep .helmignore itself +!.helmignore + +# Ignore test directories anywhere +**/test/ +**/tests/ + +# Ignore IDE files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Whitelist approach: exclude everything, then include specifics +/* +!Chart.yaml +!values.yaml +!values.schema.json +!templates/ +!charts/ +!crds/ +!files/ + +# Directory-only patterns (trailing slash) +vendor/ +node_modules/ + +# Anchored to root (leading slash) +/local-only.yaml +``` + +## Reference Implementation + +Implementation will be gated behind Charts v3 as specified in [HIP-0020][hip-0020]: + +1. New matcher package under `internal/chart/v3/ignore/` implementing `.gitignore` semantics +2. Integration with v3 chart loader +3. Comprehensive test suite covering: + - All pattern types from Git documentation + - Edge cases from reported issues ([#8688], [#3622], [#1776]) + - Cross-platform path handling +4. Migration tests validating v2 charts remain unchanged +5. Update `helm create` scaffold for v3 charts (once [PR #31592][pr-31592] merges) to generate a `.helmignore` demonstrating gitignore-parity patterns + +Implementation details (library choice, architecture) are intentionally left to implementers. A separate implementation research document will evaluate options including existing Go libraries and FluxCD's `.sourceignore` implementation. + +## Rejected Ideas + +### Shell out to `git check-ignore` + +Calling the `git` binary would provide perfect behavioral parity. Rejected because: + +- Helm binaries must not have external runtime dependencies +- Adds complexity for containerized/minimal environments +- Performance overhead for repeated invocations + +### Partial parity (e.g., add `**` but keep first-match) + +Rejected because partial fixes would leave confusing inconsistencies. Users expect `.gitignore` behavior; half-measures perpetuate the problem. + +### Change behavior for all chart versions + +Rejected because it would break existing charts that depend on current (albeit buggy) semantics. Charts v3 provides clean isolation for breaking changes. + +## Open Issues + +1. **Self-exclusion default**: Should `.helmignore` be automatically excluded in v3? (Currently requires explicit entry per [#9436]) + +2. **Global ignore file**: Users have requested `~/.helmignore` support ([#1674], [#5675]). This is out of scope for this HIP but could be addressed separately. + +3. **Scope refinement**: Should `.helmignore` have different behavior for `helm package` vs. other commands? ([#6075], [#3050]). Out of scope for this HIP. + +## References + +### Helm HIPs + +- [HIP-0020: Charts v3 Enablement][hip-0020] + +### Git Documentation + +- [git-scm.com/docs/gitignore][git-gitignore] + +### Issues Directly Addressed by This HIP + +These issues are resolved by implementing `.gitignore` pattern matching parity: + +- [#8688][#8688] — Negation semantics are inverted; `!pattern` ignores non-matches instead of re-including matches. +- [#3622][#3622] — Whitelist patterns (`/*` then `!Chart.yaml`) fail with "chart metadata missing" due to broken negation logic. +- [#1776][#1776] — Pattern `.*` incorrectly matched the current directory, breaking charts. Shows user expectation of gitignore behavior. +- [#12592][#12592] — Patterns like `charts/*/README.md` don't work; `**` glob support and improved matching would help. +- [#12265][#12265] (PR) — Attempted partial fix for negation, stalled 2 years as a breaking change. This HIP provides the proper scope via Charts v3. + +### Issues Out of Scope (Context Only) + +These issues concern _when_ `.helmignore` applies, not pattern syntax. Included for context but not addressed by this HIP: + +- [#6075][#6075] — Users expected ignore to affect only `helm package`, but it affects all commands. This is intentional; HIP clarifies but doesn't change this. +- [#3050][#3050] — Files in `.helmignore` are inaccessible to `.Files.Get`. Architectural issue about ignore scope, not pattern matching. +- [#10764][#10764] — README files consume release storage; users want "exclude from release but include in package." Requires new scope distinction. +- [#9436][#9436] — `.helmignore` doesn't exclude itself from packages. Listed as open question for Charts v3. + +### Feature Requests (Out of Scope) + +- [#1674][#1674], [#5675][#5675] — Global `~/.helmignore` support. Already partially implemented; could adopt gitignore parity separately. +- [#989][#989] — Default patterns for editor swap files. Addressed previously; shows user expectation of sensible defaults. + +### Documentation Issues + +These show user confusion that better docs (and gitignore parity) would reduce: + +- [#4638][#4638] — Requested more `.helmignore` documentation; shows need for clearer syntax docs. +- [helm-www#1171][helm-www#1171] — Docs say ignore affects "packaging" only, but it affects all operations. Needs correction regardless of HIP. +- [helm-www#1312][helm-www#1312] — Docs don't specify `.helmignore` must be in chart root, not working directory. +- [helm-www#1460][helm-www#1460] — Example pattern `/temp*` broke charts by matching `templates/`. Shows need for better examples. + +### Other Related PRs + +- [#13293][#13293] — Fix for broken symlinks in `.helmignore`. Tangentially related to ignore handling. + + + +[hip-0020]: https://github.com/helm/community/blob/main/hips/hip-0020.md +[pr-31592]: https://github.com/helm/helm/pull/31592 +[git-gitignore]: https://git-scm.com/docs/gitignore +[#8688]: https://github.com/helm/helm/issues/8688 +[#3622]: https://github.com/helm/helm/issues/3622 +[#1776]: https://github.com/helm/helm/issues/1776 +[#12592]: https://github.com/helm/helm/issues/12592 +[#6075]: https://github.com/helm/helm/issues/6075 +[#3050]: https://github.com/helm/helm/issues/3050 +[#9436]: https://github.com/helm/helm/issues/9436 +[#10764]: https://github.com/helm/helm/issues/10764 +[#1674]: https://github.com/helm/helm/issues/1674 +[#5675]: https://github.com/helm/helm/issues/5675 +[#989]: https://github.com/helm/helm/issues/989 +[#4638]: https://github.com/helm/helm/issues/4638 +[#12265]: https://github.com/helm/helm/pull/12265 +[helm-www#1171]: https://github.com/helm/helm-www/issues/1171 +[helm-www#1312]: https://github.com/helm/helm-www/issues/1312 +[helm-www#1460]: https://github.com/helm/helm-www/issues/1460 +[#13293]: https://github.com/helm/helm/pull/13293 From 4c62fb4b819b6d4e037359969fbb1e978e0ab933 Mon Sep 17 00:00:00 2001 From: Scott Rigby Date: Thu, 18 Dec 2025 07:54:34 -0500 Subject: [PATCH 2/2] clarify reasoning language Co-authored-by: Andrew Block Signed-off-by: Scott Rigby --- hips/hip-9999.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hips/hip-9999.md b/hips/hip-9999.md index 63820b11..8484cb1d 100644 --- a/hips/hip-9999.md +++ b/hips/hip-9999.md @@ -43,7 +43,7 @@ The recurring user expectation is clear: `.helmignore` should work like `.gitign ### Why .gitignore Parity? -1. **Reduced cognitive load**: Developers already know `.gitignore` semantics. Aligning `.helmignore` eliminates a class of surprises. +1. **Reduced cognitive load**: Developers already know `.gitignore` semantics. Aligning `.helmignore` enables existing knowledge and patterns to be reused with Helm. 2. **Well-documented spec**: Git's ignore format is [thoroughly documented][git-gitignore] and battle-tested across millions of repositories.