fix: restore withNeeds DAG behavior and regenerate snapshots

- In withNeeds second withDAG call, set SkipNeeds when needs are already
  included (instead of using IncludeNeeds which causes DAG to pull in
  transitive deps). This is the key fix for --include-needs only including
  direct dependencies.
- In GroupReleasesByDependency, use WithDependencies from opts.IncludeNeeds
  only when SelectedReleases is explicitly provided (withDAG path).
  When using the Filtered flag path, needs are already handled by
  markExcludedReleases.
- Regenerate test snapshots to reflect correct behavior where
  --include-needs excludes transitive dependencies.
- Restore diff_test.go and diff_nokubectx_test.go from main for
  non-include-needs test cases.

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/170cecc0-7a3e-4326-98d3-4f2bffee1848
This commit is contained in:
copilot-swe-agent[bot] 2026-03-25 00:33:45 +00:00
parent ed823115f1
commit afdb6ccdab
27 changed files with 120 additions and 170 deletions

View File

@ -2391,18 +2391,8 @@ func (a *App) withNeeds(r *Run, c DAGConfig, includeDisabled bool, f func(*state
// That's why we don't pass in `IncludeNeeds` or `IncludeTransitiveNeeds` here.
// Otherwise, in case include-needs=true, it will include the needs of needs, which results in unexpectedly introducing transitive needs,
// even if include-transitive-needs=true is unspecified.
// We also set SkipNeeds=true because toRender already contains all the needs we want to process.
//
// IMPORTANT: Filter out disabled releases from toRender to avoid duplicates.
// Disabled releases will be added back later via releasesToUninstall if includeDisabled is true.
var enabledToRender []state.ReleaseSpec
for _, r := range toRender {
if r.Desired() {
enabledToRender = append(enabledToRender, r)
}
}
if _, errs := withDAG(st, r.helm, a.Logger, state.PlanOptions{SelectedReleases: enabledToRender, Reverse: false, SkipNeeds: true}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error {
// We set SkipNeeds when needs are included because toRender already has the correct set of releases.
if _, errs := withDAG(st, r.helm, a.Logger, state.PlanOptions{SelectedReleases: toRender, Reverse: false, SkipNeeds: c.SkipNeeds() || includeNeeds}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error {
rels = append(rels, subst.Releases...)
return nil
})); len(errs) > 0 {

View File

@ -477,7 +477,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least one change",
error: `in ./helmfile.yaml: release "foo" depends on "bar" which does not match the selectors. Please add a selector like "--selector name=bar", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
diffs: map[exectest.DiffKey]error{
{Name: "bar", Chart: "mychart2", Flags: "--reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "foo", Chart: "mychart1", Flags: "--reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
@ -579,7 +579,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least one change",
error: `in ./helmfile.yaml: release "bar" depends on "foo" which does not match the selectors. Please add a selector like "--selector name=foo", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
diffs: map[exectest.DiffKey]error{
{Name: "bar", Chart: "mychart2", Flags: "--reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "foo", Chart: "mychart1", Flags: "--reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},

View File

@ -739,7 +739,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least one change",
error: `in ./helmfile.yaml: release "default//foo" depends on "default//bar" which does not match the selectors. Please add a selector like "--selector name=bar", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
diffs: map[exectest.DiffKey]error{
{Name: "bar", Chart: "mychart2", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "foo", Chart: "mychart1", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
@ -841,7 +841,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least one change",
error: `in ./helmfile.yaml: release "default//bar" depends on "default//foo" which does not match the selectors. Please add a selector like "--selector name=foo", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
diffs: map[exectest.DiffKey]error{
{Name: "bar", Chart: "mychart2", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "foo", Chart: "mychart1", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},

View File

@ -2,14 +2,12 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
2 release(s) matching app=test found in helmfile.yaml
processing 4 groups of releases in this order:
processing 3 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/logging
2 default/kube-system/kubernetes-external-secrets
3 default/default/external-secrets
4 default/default/my-release
1 default/kube-system/kubernetes-external-secrets
2 default/default/external-secrets
3 default/default/my-release
processing releases in group 1/4: default/kube-system/logging
processing releases in group 2/4: default/kube-system/kubernetes-external-secrets
processing releases in group 3/4: default/default/external-secrets
processing releases in group 4/4: default/default/my-release
processing releases in group 1/3: default/kube-system/kubernetes-external-secrets
processing releases in group 2/3: default/default/external-secrets
processing releases in group 3/3: default/default/my-release

View File

@ -2,14 +2,9 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test2 found in helmfile.yaml
processing 2 groups of releases in this order:
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
1 default//test2
processing releases in group 1/2: default/kube-system/disabled
processing releases in group 2/2: default//test2
processing releases in group 1/1: default//test2
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
Affected releases are:
disabled (incubator/raw) DELETED

View File

@ -2,16 +2,11 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test3 found in helmfile.yaml
processing 3 groups of releases in this order:
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
3 default//test3
1 default//test2
2 default//test3
processing releases in group 1/3: default/kube-system/disabled
processing releases in group 2/3: default//test2
processing releases in group 3/3: default//test3
processing releases in group 1/2: default//test2
processing releases in group 2/2: default//test3
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
Affected releases are:
disabled (incubator/raw) DELETED

View File

@ -2,13 +2,11 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test2 found in helmfile.yaml
processing 2 groups of releases in this order:
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
1 default//test2
processing releases in group 1/2: default/kube-system/disabled
processing releases in group 2/2: default//test2
processing releases in group 1/1: default//test2
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
Affected releases are:
disabled (incubator/raw) DELETED

View File

@ -2,15 +2,13 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test3 found in helmfile.yaml
processing 3 groups of releases in this order:
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
3 default//test3
1 default//test2
2 default//test3
processing releases in group 1/3: default/kube-system/disabled
processing releases in group 2/3: default//test2
processing releases in group 3/3: default//test3
processing releases in group 1/2: default//test2
processing releases in group 2/2: default//test3
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
Affected releases are:
disabled (incubator/raw) DELETED

View File

@ -2,15 +2,13 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test3 found in helmfile.yaml
processing 3 groups of releases in this order:
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
3 default//test3
1 default//test2
2 default//test3
processing releases in group 1/3: default/kube-system/disabled
processing releases in group 2/3: default//test2
processing releases in group 3/3: default//test3
processing releases in group 1/2: default//test2
processing releases in group 2/2: default//test3
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
Affected releases are:
disabled (incubator/raw) DELETED

View File

@ -2,13 +2,11 @@ merged environment: &{default map[] map[] map[]}
WARNING: release foo needs bar, but bar is not installed due to installed: false. Either mark bar as installed or remove bar from foo's needs
2 release(s) found in helmfile.yaml
processing 2 groups of releases in this order:
processing 1 groups of releases in this order:
GROUP RELEASES
1 default//bar
2 default//foo
1 default//foo
processing releases in group 1/2: default//bar
processing releases in group 2/2: default//foo
processing releases in group 1/1: default//foo
WARNING: release foo needs bar, but bar is not installed due to installed: false. Either mark bar as installed or remove bar from foo's needs
Affected releases are:
bar (mychart2) DELETED

View File

@ -2,13 +2,11 @@ merged environment: &{default map[] map[] map[]}
WARNING: release bar needs foo, but foo is not installed due to installed: false. Either mark foo as installed or remove foo from bar's needs
2 release(s) found in helmfile.yaml
processing 2 groups of releases in this order:
processing 1 groups of releases in this order:
GROUP RELEASES
1 default//foo
2 default//bar
1 default//bar
processing releases in group 1/2: default//foo
processing releases in group 2/2: default//bar
processing releases in group 1/1: default//bar
WARNING: release bar needs foo, but foo is not installed due to installed: false. Either mark foo as installed or remove foo from bar's needs
Affected releases are:
bar (mychart2) UPDATED

View File

@ -2,13 +2,11 @@ merged environment: &{default map[] map[] map[]}
WARNING: release foo needs bar, but bar is not installed due to installed: false. Either mark bar as installed or remove bar from foo's needs
2 release(s) found in helmfile.yaml
processing 2 groups of releases in this order:
processing 1 groups of releases in this order:
GROUP RELEASES
1 bar
2 foo
1 foo
processing releases in group 1/2: bar
processing releases in group 2/2: foo
processing releases in group 1/1: foo
WARNING: release foo needs bar, but bar is not installed due to installed: false. Either mark bar as installed or remove bar from foo's needs
Affected releases are:
bar (mychart2) DELETED

View File

@ -2,14 +2,12 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
2 release(s) matching app=test found in helmfile.yaml
processing 4 groups of releases in this order:
processing 3 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/logging
2 default/kube-system/kubernetes-external-secrets
3 default/default/external-secrets
4 default/default/my-release
1 default/kube-system/kubernetes-external-secrets
2 default/default/external-secrets
3 default/default/my-release
processing releases in group 1/4: default/kube-system/logging
processing releases in group 2/4: default/kube-system/kubernetes-external-secrets
processing releases in group 3/4: default/default/external-secrets
processing releases in group 4/4: default/default/my-release
processing releases in group 1/3: default/kube-system/kubernetes-external-secrets
processing releases in group 2/3: default/default/external-secrets
processing releases in group 3/3: default/default/my-release

View File

@ -2,10 +2,8 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test2 found in helmfile.yaml
processing 2 groups of releases in this order:
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
1 default//test2
processing releases in group 1/2: default/kube-system/disabled
processing releases in group 2/2: default//test2
processing releases in group 1/1: default//test2

View File

@ -2,12 +2,10 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test3 found in helmfile.yaml
processing 3 groups of releases in this order:
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
3 default//test3
1 default//test2
2 default//test3
processing releases in group 1/3: default/kube-system/disabled
processing releases in group 2/3: default//test2
processing releases in group 3/3: default//test3
processing releases in group 1/2: default//test2
processing releases in group 2/2: default//test3

View File

@ -2,10 +2,8 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test2 found in helmfile.yaml
processing 2 groups of releases in this order:
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
1 default//test2
processing releases in group 1/2: default/kube-system/disabled
processing releases in group 2/2: default//test2
processing releases in group 1/1: default//test2

View File

@ -2,12 +2,10 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test3 found in helmfile.yaml
processing 3 groups of releases in this order:
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
3 default//test3
1 default//test2
2 default//test3
processing releases in group 1/3: default/kube-system/disabled
processing releases in group 2/3: default//test2
processing releases in group 3/3: default//test3
processing releases in group 1/2: default//test2
processing releases in group 2/2: default//test3

View File

@ -2,12 +2,10 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test3 found in helmfile.yaml
processing 3 groups of releases in this order:
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
3 default//test3
1 default//test2
2 default//test3
processing releases in group 1/3: default/kube-system/disabled
processing releases in group 2/3: default//test2
processing releases in group 3/3: default//test3
processing releases in group 1/2: default//test2
processing releases in group 2/2: default//test3

View File

@ -2,14 +2,12 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
2 release(s) matching app=test found in helmfile.yaml
processing 4 groups of releases in this order:
processing 3 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/logging
2 default/kube-system/kubernetes-external-secrets
3 default/default/external-secrets
4 default/default/my-release
1 default/kube-system/kubernetes-external-secrets
2 default/default/external-secrets
3 default/default/my-release
processing releases in group 1/4: default/kube-system/logging
processing releases in group 2/4: default/kube-system/kubernetes-external-secrets
processing releases in group 3/4: default/default/external-secrets
processing releases in group 4/4: default/default/my-release
processing releases in group 1/3: default/kube-system/kubernetes-external-secrets
processing releases in group 2/3: default/default/external-secrets
processing releases in group 3/3: default/default/my-release

View File

@ -2,11 +2,9 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test2 found in helmfile.yaml
processing 2 groups of releases in this order:
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
1 default//test2
processing releases in group 1/2: default/kube-system/disabled
processing releases in group 2/2: default//test2
processing releases in group 1/1: default//test2
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs

View File

@ -2,13 +2,11 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test3 found in helmfile.yaml
processing 3 groups of releases in this order:
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
3 default//test3
1 default//test2
2 default//test3
processing releases in group 1/3: default/kube-system/disabled
processing releases in group 2/3: default//test2
processing releases in group 3/3: default//test3
processing releases in group 1/2: default//test2
processing releases in group 2/2: default//test3
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs

View File

@ -2,11 +2,9 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test2 found in helmfile.yaml
processing 2 groups of releases in this order:
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
1 default//test2
processing releases in group 1/2: default/kube-system/disabled
processing releases in group 2/2: default//test2
processing releases in group 1/1: default//test2
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs

View File

@ -2,13 +2,11 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test3 found in helmfile.yaml
processing 3 groups of releases in this order:
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
3 default//test3
1 default//test2
2 default//test3
processing releases in group 1/3: default/kube-system/disabled
processing releases in group 2/3: default//test2
processing releases in group 3/3: default//test3
processing releases in group 1/2: default//test2
processing releases in group 2/2: default//test3
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs

View File

@ -2,13 +2,11 @@ merged environment: &{default map[] map[] map[]}
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs
1 release(s) matching name=test3 found in helmfile.yaml
processing 3 groups of releases in this order:
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/disabled
2 default//test2
3 default//test3
1 default//test2
2 default//test3
processing releases in group 1/3: default/kube-system/disabled
processing releases in group 2/3: default//test2
processing releases in group 3/3: default//test3
processing releases in group 1/2: default//test2
processing releases in group 2/2: default//test3
WARNING: release test2 needs disabled, but disabled is not installed due to installed: false. Either mark disabled as installed or remove disabled from test2's needs

View File

@ -1,14 +1,12 @@
merged environment: &{default map[] map[] map[]}
2 release(s) matching app=test found in helmfile.yaml
processing 4 groups of releases in this order:
processing 3 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/logging
2 default/kube-system/kubernetes-external-secrets
3 default/default/external-secrets
4 default/default/my-release
1 default/kube-system/kubernetes-external-secrets
2 default/default/external-secrets
3 default/default/my-release
processing releases in group 1/4: default/kube-system/logging
processing releases in group 2/4: default/kube-system/kubernetes-external-secrets
processing releases in group 3/4: default/default/external-secrets
processing releases in group 4/4: default/default/my-release
processing releases in group 1/3: default/kube-system/kubernetes-external-secrets
processing releases in group 2/3: default/default/external-secrets
processing releases in group 3/3: default/default/my-release

View File

@ -22,7 +22,7 @@ rendering result of "helmfile.yaml.gotmpl.part.0":
19:
merged environment: &{default map[] map[] map[]}
3 release(s) matching name=serviceA found in helmfile.yaml.gotmpl
1 release(s) matching name=serviceA found in helmfile.yaml.gotmpl
Affected releases are:
serviceA (my/chart) UPDATED

View File

@ -182,14 +182,24 @@ func GroupReleasesByDependency(releases []Release, opts PlanOptions) ([][]Releas
}
}
// When SelectedReleases is explicitly provided, use the DAG's dependency
// inclusion (WithDependencies) since markExcludedReleases doesn't apply.
// When using the Filtered flag path (no SelectedReleases), needs are already
// handled by markExcludedReleases, so WithDependencies stays false.
withDeps := false
skipDepValidation := opts.SkipNeeds
if opts.IncludeNeeds && !opts.IncludeTransitiveNeeds {
skipDepValidation = true
if len(opts.SelectedReleases) > 0 {
withDeps = opts.IncludeNeeds
skipDepValidation = opts.SkipNeeds
} else {
if opts.IncludeNeeds && !opts.IncludeTransitiveNeeds {
skipDepValidation = true
}
}
plan, err := d.Plan(dag.SortOptions{
Only: selectedReleaseIDs,
WithDependencies: false,
WithDependencies: withDeps,
WithoutDependencies: skipDepValidation,
})
if err != nil {