Skip to content

fix: preserve manually added files when dir contains fileTree and file nodes#483

Open
VelmiraS wants to merge 2 commits into
masterfrom
fix/filetree-manual-file-lost
Open

fix: preserve manually added files when dir contains fileTree and file nodes#483
VelmiraS wants to merge 2 commits into
masterfrom
fix/filetree-manual-file-lost

Conversation

@VelmiraS

Copy link
Copy Markdown
Contributor

Summary

  • RemoveNodeFromParent used a swap-and-truncate pattern to remove nodes, which silently deleted the last sibling of the removed fileTree node
  • Replaced with order-preserving deletion (append(s[:i], s[i+1:]...)) so manually added file: nodes are not lost during fileTree expansion
  • Updated existing result files to reflect the now-correct stable ordering
  • Added regression test covering a dir with both fileTree and a manually added file entry

Fixes the bug reported where a manually added file appears at the top level of the TOC instead of inside its parent directory's subnav.

Test plan

  • go test ./pkg/manifest/... — 6/6 pass
  • go test ./... — all pass
  • Run gen-toc with a manifest containing a dir with both fileTree: and file: — manually added file appears under the correct directory in the TOC

…e nodes

RemoveNodeFromParent used swap-and-truncate which silently deleted the
last sibling of a removed fileTree node. Replace with order-preserving
deletion so manually added file nodes are not lost during fileTree
expansion.

Add regression test covering a dir with both fileTree and a manually
added file entry.
@gardener-prow

gardener-prow Bot commented Jul 17, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign klocke-io for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@gardener-prow gardener-prow Bot added do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jul 17, 2026
@VelmiraS VelmiraS self-assigned this Jul 17, 2026
RemoveNodeFromParent inside a range loop shifted the underlying array
while the range header still held the original length, causing elements
to be visited twice and triggering a false file-collision error.

Collect nodes to remove during the loop and delete them after.
Comment thread pkg/manifest/manifest.go
size := len(parent.Structure)
parent.Structure[i] = parent.Structure[size-1]
parent.Structure = parent.Structure[:size-1]
parent.Structure = append(parent.Structure[:i], parent.Structure[i+1:]...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be editing the parent structure we are iterating on. For me, this always ends in a bug.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that mutating a slice during range is risky. In RemoveNodeFromParent it's safe because append is immediately followed by return — the loop stops right there, so no index shifting or double-visiting can happen.

The real mutation-during-range risk was in mergeFolders — and the second commit addresses exactly that: nodes are collected into toRemove during the loop and only removed after, so we never modify the slice we're iterating over.

Comment thread pkg/manifest/manifest.go
nodeNameToNode[child.File] = child
}
}
for _, child := range toRemove {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase 1 – We loop through node.Structure with for _, child := range node.Structure. During this loop, we do not change node.Structure. If we find a node that should be removed, we only do toRemove = append(toRemove, child). toRemove is a different slice, so it does not affect node.Structure. The loop continues safely because node.Structure does not change while we are iterating over it.

Phase 2 – After the first loop is completely finished, we start a second loop: for _, child := range toRemove. Now it is safe to modify node.Structure because we are no longer iterating over it.
The old version of the remove function moved the last element into the removed element's position.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants