Skip to content

Comments

New doc-builder changelog remove command#2773

Merged
lcawl merged 12 commits intomainfrom
changelog-rm
Feb 24, 2026
Merged

New doc-builder changelog remove command#2773
lcawl merged 12 commits intomainfrom
changelog-rm

Conversation

@lcawl
Copy link
Contributor

@lcawl lcawl commented Feb 24, 2026

Summary

Add a new docs-builder changelog remove command that enables teams to clean up changelog files using the same filtering semantics as changelog bundle.
This PR also fixes a silent-failure bug in the {changelog} directive.

Notes for reviewers

If you disagree with the change to the directive's handling of missing changelogs (i.e. you want to return to having warning instead of failure), lmk. I don't think it's required for this PR but accepted the recommendation as part of the AI plan.

Changes

New: changelog remove command

A new docs-builder changelog remove command lets you clean up individual changelog YAML files from a directory using filter options consistent with changelog bundle:

  • --all — remove every changelog in the directory
  • --products "product target lifecycle" — remove changelogs matching product/target/lifecycle (wildcards supported)
  • --prs — remove changelogs referencing the specified pull requests
  • --issues — remove changelogs referencing the specified issues

Exactly one filter must be specified.

Bundle dependency checking: Before deleting, the command scans for bundles that still hold unresolved file: references to the matched changelogs. If any are found, the command blocks and emits an error per dependency. This prevents the {changelog} directive from failing at build time with missing-file errors. Use --force to override and emit warnings instead.

Dry-run support: --dry-run prints what would be removed (and any bundle dependency conflicts) without touching the filesystem.

Additional options: all matching the conventions of changelog bundle:

  • --config — Path to changelog.yml (default: docs/changelog.yml)
  • --directory — Directory containing changelog YAML files (default: from config bundle.directory or current directory)
  • --owner — Required when PRs/issues are specified as bare numbers
  • --dry-run — Print files that would be removed; do not delete
  • --bundles-dir — Override for bundles folder used in dependency check

Fix: {changelog} directive now emits errors for missing file references

Previously, when a bundle contained an unresolved file: reference pointing to a changelog that no longer existed on disk, the directive silently omitted those entries. The bundle loader callback was changed from EmitWarning to EmitError, causing the build to fail fast rather than producing incomplete release notes without any indication of the problem.

Files changed

File Change
src/services/Elastic.Changelog/Bundling/ChangelogRemoveService.cs New — core remove logic: argument validation, filter application, bundle dependency discovery, file deletion
src/tooling/docs-builder/Commands/ChangelogCommand.cs Added [Command("remove")] subcommand with full argument parsing
src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs Changed EmitWarningEmitError for missing bundle file references
tests/Elastic.Changelog.Tests/Changelogs/ChangelogRemoveTests.cs New — 14 tests covering all filter modes, dry-run, no-match error, bundle blocking, --force, and bundle-dir override
tests/Elastic.Markdown.Tests/Directives/ChangelogMissingFileTests.cs New — 3 tests verifying the directive emits errors (not warnings) for missing references, and no errors for inline/existing entries
docs/cli/release/changelog-remove.md New — CLI reference page
docs/contribute/changelog.md Added "Remove changelog files" section with usage examples
docs/syntax/changelog.md Added "Error behavior for missing referenced files" section; added changelog-remove to the Related list
docs/_docset.yml Added changelog-remove.md to the release folder TOC

Implementation details

Reuse strategy

The pipeline is identical to changelog bundle up to the point of writing output. Instead of BundleBuilder producing a YAML file, the matched MatchedChangelogFile paths are deleted:

ChangelogConfigurationLoader   (already exists)
PrFilterLoader / IssueFilterLoader  (already exists)
ChangelogFileDiscovery         (already exists)
ChangelogEntryMatcher          (already exists)
      ↓ matched file paths
ChangelogRemoveService         (new)
  └─ BundleDependencyChecker   (new — blocks or warns before delete)
  └─ FileSystem.Delete / dry-run list

New components

ChangelogRemoveService in src/services/Elastic.Changelog/Bundling/ChangelogRemoveService.cs:

  • ChangelogRemoveArguments record defined at top of this file (consistent with BundleChangelogsArguments)
  • Core method: RemoveChangelogs(collector, args, ctx)
  • Applies same config defaults as bundle (ApplyConfigDefaults)
  • Applies same filter validation (exactly one of --all/--products/--prs/--issues)
  • After matching: calls BundleDependencyChecker, blocks if dependencies found (unless --force)
  • If --dry-run: logs file list, returns without deleting

BundleDependencyChecker (private helper in ChangelogRemoveService.cs):

  • Locates bundles automatically: (1) config bundle.output_directory, (2) {directory}/bundles, (3) {directory}/../bundles
  • --bundles-dir overrides this discovery
  • Deserializes each bundle, collects all entry.File.Name values (unresolved file-reference entries only; inline/resolved entries are safe to remove)
  • Compares against files to remove (normalizing path separators)
  • Returns IReadOnlyList<(string changelogFile, string bundleFile)> dependency pairs

Bundle Dependency Check — Detail

Discovery order (automatic)

  1. --bundles-dir explicit override
  2. Config bundle.output_directory if set
  3. {directory}/bundles if that directory exists
  4. {directory}/../bundles if that directory exists
  5. If no bundles found at any location — skip check, proceed normally

What is checked

Only entry.File?.Name references matter. Entries with inline title/type data (created with --resolve) embed content in the bundle and do not depend on the external files at build time.

Path comparison: normalize both sides to use the same separator. Bundle-relative paths are treated as relative to the changelog directory (parent of the bundles folder), consistent with how BundleLoader.cs lines 82–84 resolves them.

Behavior

Without --force: any file referenced by an unresolved bundle causes an error and the command aborts before deleting anything.

With --force: emit a warning per affected file and proceed.

With --dry-run: run the dependency check and print conflicts, but do not delete.

Error message format

Error: Changelog file '1234-fix.yaml' is referenced by unresolved bundle 'releases/9.3.0.yaml'.
Removing it will cause the {changelog} directive to fail when loading that bundle.
To proceed anyway, use --force. To make the bundle self-contained, re-run:
  docs-builder changelog bundle --resolve ...

Directive Error Messages for Missing Changelogs (independent fix)

Current behavior

BundleLoader.ResolveEntries accepts an Action<string> emitWarning callback. When a file reference is missing, it calls emitWarning(...) and skips the entry. The directive passes a warning callback; the render command uses BundleValidationService which errors.

Fix (one line in ChangelogBlock)

In ChangelogBlock.cs LoadAndCacheBundles, change the callback passed to BundleLoader:

// Before: emits warning, build continues silently
var loadedBundles = loader.LoadBundles(BundlesFolderPath, msg => this.EmitWarning(msg));

// After: emits error, build fails on missing referenced changelogs
var loadedBundles = loader.LoadBundles(BundlesFolderPath, msg => this.EmitError(msg));

No changes to BundleLoader itself are needed — the callback is already the right extension point.


Running tests

dotnet test tests/Elastic.Changelog.Tests/ --filter "FullyQualifiedName~ChangelogRemove"
dotnet test tests/Elastic.Markdown.Tests/ --filter "FullyQualifiedName~Changelog"

Steps to test

I tested this PR against files in elastic/kibana#250840

  1. Create a test bundle that doesn't have resolved content. For example:
    docs-builder changelog bundle \                                                                                                  
    --config ~/Documents/GitHub/kibana/docs/changelog.yml \
    --input-products "kibana 9.3.0 *" \
    --directory ~/Documents/GitHub/kibana/docs/changelog \
    --output ~/Documents/GitHub/kibana/docs/releases/kibana/9.10.0.yaml \
    --repo kibana --owner elastic \
    --output-products "kibana 9.10.0 *" \
    --hide-features test_feature1 --no-resolve
    NOTE: There's a bug where --no-resolve was ignored so I worked around it by setting bundle.resolve: false in the configuration file. Will pursue in separate PR: Fix changelog bundle --no-resolve #2774
  2. Test changelog removal with the --prs option:
    ./docs-builder changelog remove \
    --config ~/Documents/GitHub/kibana/docs/changelog.yml \
    --prs 238646 --repo kibana --owner elastic \
    --bundles-dir ~/Documents/GitHub/kibana/docs/releases \
    --directory ~/Documents/GitHub/kibana/docs/changelog
    It correctly returns the following error:
    Error: Changelog file '238646.yaml' is referenced by unresolved bundle '/path/to/kibana/docs/releases/kibana/9.10.0.yaml'. Removing it will cause the {changelog} directive to fail when loading that bundle. To make the bundle self-contained, re-run: docs-builder changelog bundle --resolve ... To proceed anyway, use --force...
    If I rerun the command with the --force option, it correctly returns the warning but deletes the file:
    info ::e.c.b.logRemoveService:: Removed: /path/to/kibana/docs/changelog/238646.yaml
    info ::e.c.b.logRemoveService:: Removed 1 changelog file(s).
    The following errors and warnings were found in the documentation
    Warning: Changelog file '238646.yaml' is referenced by unresolved bundle '/path/to/kibana/docs/releases/kibana/9.10.0.yaml'. Removing it will cause the {changelog} directive to fail when loading that bundle. To make the bundle self-contained, re-run: docs-builder changelog bundle --resolve ... 
  3. Test changelog removal with the --issues option. For example:
    ./docs-builder changelog remove --config \
    ~/Documents/GitHub/kibana/docs/changelog.yml  \
    --bundles-dir ~/Documents/GitHub/kibana/docs/releases \
    --directory ~/Documents/GitHub/kibana/docs/changelog \
    --issues 244428 --repo kibana --owner elastic
  4. Test changelog removal with the --products and --dry-run options. For example:
    ./docs-builder changelog remove \
    --config ~/Documents/GitHub/kibana/docs/changelog.yml  \
    --bundles-dir ~/Documents/GitHub/kibana/docs/releases \
    --directory ~/Documents/GitHub/kibana/docs/changelog \
    --products "kibana 9.3.0 *,cloud-serverless 2026-02-23 *" \
    --dry-run
    The command correctly indicates all the files that exist in the unresolved bundle:
    ...
    Error: Changelog file '246941.yaml' is referenced by unresolved bundle '/path/to/kibana/docs/releases/kibana/9.10.0.yaml'. Removing it will cause the {changelog} directive to fail when loading that bundle. To make the bundle self-contained, re-run: docs-builder changelog bundle --resolve ... To proceed anyway, use --force...
  5. Remove the unresolved test bundle.
  6. Test changelog removal with the --all and --dry-run options. For example:
    ./docs-builder changelog remove \
    --config ~/Documents/GitHub/kibana/docs/changelog.yml  \
    --bundles-dir ~/Documents/GitHub/kibana/docs/releases \
    --directory ~/Documents/GitHub/kibana/docs/changelog \
    --all --dry-run
    The command correctly lists all the files that would be deleted (since there are no longer any unresolved bundles):
    ...
    info ::e.c.b.logRemoveService:: [dry-run] Would remove 532 changelog file(s):
    info ::e.c.b.logRemoveService:: [dry-run]   /path/to/kibana/docs/changelog/244072.yaml
    info ::e.c.b.logRemoveService:: [dry-run]   /path/to/kibana/docs/changelog/242325.yaml
  7. Test the removal with the --prs option and a newline delimited text file. For example:
     ./docs-builder changelog remove \
     --config ~/Documents/GitHub/kibana/docs/changelog.yml  \
     --bundles-dir ~/Documents/GitHub/kibana/docs/releases \
     --directory ~/Documents/GitHub/kibana/docs/changelog \
     --prs ~/Documents/GitHub/kibana/docs/test.txt
    When the file contains only PR numbers not URLs and I get the following error:
    Error: When --prs contains PR numbers (not URLs or owner/repo#number format), both --owner and --repo must be provided
    The command succeeds when I add the missing --repo and --owner options and gives a warning when my list of PRs has some that don't exist in the changelogs. For example:
    info ::e.c.b.logRemoveService:: Removed: /path/to/docs/changelog/224552.yaml
    info ::e.c.b.logRemoveService:: Removed 1 changelog file(s).
    The following errors and warnings were found in the documentation
    Warning: No changelog file found for PR: 224569

Generative AI disclosure

  1. Did you use a generative AI (GenAI) tool to assist in creating this contribution?
  • Yes
  • No
  1. If you answered "Yes" to the previous question, please specify the tool(s) and model(s) used (e.g., Google Gemini, OpenAI ChatGPT-4, etc.).

Tool(s) and model(s) used: composer-1.5, claude-4.6-sonnet-medium

@github-actions
Copy link

github-actions bot commented Feb 24, 2026

lcawl and others added 2 commits February 23, 2026 16:40
… combined'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
lcawl and others added 2 commits February 23, 2026 16:53
…ect'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
lcawl and others added 2 commits February 23, 2026 17:09
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
lcawl and others added 2 commits February 23, 2026 18:11
…ect'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Comment on lines +294 to +308
foreach (var entryFileName in entryFileNames.Where(entryFileName => toRemoveNames.Contains(entryFileName)))
{
// bundle entry.File.Name is relative to the changelog directory (parent of bundles dir)
// Normalize to just the base filename for comparison

// Find the full path from filesToRemove that matches this entry
var matchingFile = filesToRemove
.FirstOrDefault(f => string.Equals(
_fileSystem.Path.GetFileName(f),
entryFileName,
StringComparison.OrdinalIgnoreCase));

if (matchingFile is not null)
dependencies.Add(new BundleDependency(matchingFile, bundleFile));
}
@lcawl lcawl marked this pull request as ready for review February 24, 2026 02:37
@lcawl lcawl requested review from a team as code owners February 24, 2026 02:37
@lcawl lcawl requested a review from reakaleek February 24, 2026 02:37
Copy link
Member

@Mpdreamz Mpdreamz left a comment

Choose a reason for hiding this comment

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

LGTM i was picturing this more as:

docs-builder changelog remove <bundle_profile> <version>

to pair with the create-bundle command support for bundle profiles.

@lcawl
Copy link
Contributor Author

lcawl commented Feb 24, 2026

LGTM i was picturing this more as:

docs-builder changelog remove <bundle_profile> <version>

to pair with the create-bundle command support for bundle profiles.

I can add that in a subsequent PR. I need to play with those profiles more anyway since I honestly haven't done a test scenario with them yet.

@lcawl lcawl merged commit faa5325 into main Feb 24, 2026
30 checks passed
@lcawl lcawl deleted the changelog-rm branch February 24, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants