diff --git a/hips/hip-9999.md b/hips/hip-9999.md new file mode 100644 index 00000000..9e1e9fed --- /dev/null +++ b/hips/hip-9999.md @@ -0,0 +1,349 @@ +--- +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"] +--- + +## 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. **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] +- **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` 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. + +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 surprising) behavior. + +## Specification + +### Behavioral Parity with .gitignore + +Charts v3 `.helmignore` files should support the full `.gitignore` pattern format as documented at [git-scm.com/docs/gitignore][git-gitignore]. + +#### Design Intent + +`.helmignore` is intended to follow Git's `.gitignore` file pattern matching semantics as documented at the time of this HIP's acceptance. These semantics have been intentionally stable for many years, and familiarity with them is a primary goal of this proposal. + +If Helm's `.helmignore` behavior diverges from Git's documented `.gitignore` behavior, that divergence should be treated as a bug and corrected—unless the divergence is explicitly documented in this proposal (see Scope Clarification). + +**Future Git changes**: If Git were to introduce incompatible changes to `.gitignore` matching semantics in the future, Helm would evaluate and explicitly decide whether to adopt those changes. Helm does not implicitly inherit future Git behavior changes. + +#### 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 all top-level entries (files and directories) +/* + +# 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. + +#### File Location + +Charts v3 `.helmignore` is loaded from the chart root directory only. Unlike `.gitignore`, which supports recursive ignore files in subdirectories within a repository, Helm loads `.helmignore` only from the chart root. Patterns in the root `.helmignore` apply to the entire chart tree. + +Recursive `.helmignore` loading within a chart (matching Git's nested `.gitignore` behavior) is a potential future enhancement but is out of scope for this HIP. + +### 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). + +### Integration Points + +The new `.gitignore`-parity matching should apply in all existing chart file operations (unchanged scope): + +- `helm package` +- `helm lint` +- `helm template` +- `helm install` / `helm upgrade` (for local charts) +- `.Files.Get` / `.Files.Glob` (file access in templates) + +This list is representative; the matching logic applies wherever chart files are loaded or accessed. + +### 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) | + +## 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 background documentation for the pattern semantics that `.helmignore` follows. + +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 + +5. **Improve v2 and v3 documentation**: Existing `.helmignore` documentation is insufficient, leading to end user confusion, so this should be documented as well. + +6. **File location**: Clarify that `.helmignore` must be in the chart root directory. Unlike `.gitignore`, nested `.helmignore` files are not loaded. This existing behavior is unchanged. + +### Scaffold Update + +Update the default `.helmignore` generated by `helm create` for Charts v3. The current scaffold has been largely unchanged since 2016 ([helm#1028][#1028]). The v3 scaffold should demonstrate gitignore-parity patterns (e.g., `**` globs, whitelist patterns with `!`). + +### 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 + +A proof-of-concept is available at [github.com/scottrigby/helmignore-ref][helmignore-ref], demonstrating that `go-git/go-git/v5/plumbing/format/gitignore` meets all HIP requirements with minimal wrapper code (~76 lines). The repository includes research documentation comparing library options. + +Implementation will be gated behind Charts v3 as specified in [HIP-0020][hip-0020]: + +1. New matcher package under `internal/chart/v3/ignore/` using go-git's gitignore library +2. Integration with v3 chart loader +3. Test suite covering all pattern types from this specification +4. Migration tests validating v2 charts remain unchanged +5. Update `helm create` scaffold for v3 charts (once [PR #31592][pr-31592] merges) + +## 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 surprising) semantics. Charts v3 provides clean isolation for breaking changes. + +## Open Issues + +### Subchart .helmignore Handling + +Should `.helmignore` files in subchart directories (`charts/*/`) be respected when processing an umbrella chart? + +**Current behavior**: Only the parent chart's `.helmignore` is loaded; subchart `.helmignore` files are ignored. + +**Open question**: Is this intended behavior, a bug, or an oversight? Arguments exist both ways: + +- **For respecting**: Chart authors expect their ignore rules to apply +- **Against (or: may not matter)**: Parent chart may want control; also, helm-ignored files would already be excluded when the subchart was packaged, so they likely won't be present anyway + +**Action**: Investigate history (git commits, meeting notes, previous maintainers) and discuss with community before deciding whether to change this behavior. For now, this HIP preserves existing behavior. + +## References + +### Helm HIPs + +- [HIP-0020: Charts v3 Enablement][hip-0020] + +### Git Documentation + +- [git-scm.com/docs/gitignore][git-gitignore] + +### Libraries + +- [go-git/go-git gitignore][go-git-gitignore] — Recommended implementation library + +### 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. This is intentional becuase `.helmignore` is used for more than packaging, so various workflows could be disrupted (see https://github.com/helm/helm/issues/9436#issuecomment-792795063). Documentation update via this HIP should clarify this. + +### Feature Requests (Out of Scope) + +- [#1674][#1674], [#5675][#5675] — Global `~/.helmignore` support. Already partially implemented; could adopt gitignore parity separately. + +### 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 +[helmignore-ref]: https://github.com/scottrigby/helmignore-ref +[pr-31592]: https://github.com/helm/helm/pull/31592 +[git-gitignore]: https://git-scm.com/docs/gitignore +[go-git-gitignore]: https://github.com/go-git/go-git/tree/main/plumbing/format/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 +[#1028]: https://github.com/helm/helm/pull/1028 +[#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