Skip to content

Add one-shot changelog migration script for per-entry layout#45092

Draft
Copilot wants to merge 4 commits into
mainfrom
copilot/add-conversion-script-for-changelog
Draft

Add one-shot changelog migration script for per-entry layout#45092
Copilot wants to merge 4 commits into
mainfrom
copilot/add-conversion-script-for-changelog

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 15, 2026

This PR adds a pre-migration converter so we can transform the legacy monolithic changelogs/current.yaml into per-entry changelog files when ready, without switching the repo layout now. It produces per-entry .rst files, a generated areas.yaml, and rewrites current.yaml to date-only form.

  • Migration script (tools/changelogs/migrate_to_per_entry.py)

    • Reads:
      • changelogs/current.yaml
      • changelogs/sections.yaml
    • Writes:
      • changelogs/current/<section>/<area_encoded>__<slug>.rst
      • changelogs/areas.yaml
      • rewritten changelogs/current.yaml with only date: ...
    • Preserves existing date value (including non-Pending values).
  • Per-entry filename generation

    • Section from YAML key.
    • Area encoding: /~ (supports nested areas).
    • Slug generation from change text:
      • first sentence, capped at 60 chars source
      • lowercase + non-alnum runs collapsed to -
      • trimmed + truncated to ~40 chars at word boundary
      • deterministic collision suffixes: -2, -3, ...
  • Entry content handling

    • Writes change block content as-is (no reflow).
    • Ensures exactly one trailing newline in each generated .rst.
    • Adds explicit input/content validation for missing required files and empty area / change entries.
  • Generated areas.yaml

    • Built from unique area values observed in current.yaml.
    • Includes canonical header comments and stable sorted output.
  • Focused unit coverage (tools/changelogs/migrate_to_per_entry_test.py)

    • Validates area encoding, slugging/collision behavior, output filenames/content, areas.yaml shape, and date preservation.
entry_path = current_entries_dir / section / f"{area.replace('/', '~')}__{slug}.rst"
Original prompt

Context

We're preparing to migrate Envoy's changelog from a single changelogs/current.yaml file to a per-entry layout under changelogs/current/<section>/<area>__<slug>.rst. The toolshed-side work is tracked in envoyproxy/toolshed#4499 (plan: plans/per-entry-changelog-4498.md).

This PR does not flip the layout. It only adds a one-shot conversion script that we'll use when we're ready to migrate. Landing this in advance is much safer than building the script under time pressure during the actual migration. The script may not even be merged — that's fine. The point is to have a reviewed, tested artifact ready to run.

What to build

Add a Python script at tools/changelogs/migrate_to_per_entry.py that converts the legacy single-file changelog into the per-entry layout.

Inputs

  • changelogs/current.yaml (existing format — see current.yaml)
  • changelogs/sections.yaml (canonical section list — see sections.yaml)

Outputs

  1. changelogs/current/<section>/<area_encoded>__<slug>.rst — one file per entry.

    • <section> is the YAML key (e.g. bug_fixes, new_features, minor_behavior_changes).
    • <area_encoded> is the entry's area: field with / replaced by ~ (e.g. extensions/filters/http/oauth2extensions~filters~http~oauth2). All current entries in current.yaml use simple single-segment areas, but the script MUST handle nested ones.
    • <slug> is auto-generated from the entry's change: text:
      • Take the first sentence (split on . , or first 60 chars, whichever is shorter).
      • Lowercase, replace runs of non-alphanumeric chars with -, strip leading/trailing -.
      • Truncate to ~40 chars at a word boundary.
      • If a collision occurs (same section + same area + same slug), append -2, -3, … to disambiguate. Deterministic ordering based on appearance order in current.yaml.
    • File content is the entry's change: block, written as-is (preserve the existing RST/multiline formatting; do NOT reflow).
    • No leading/trailing blank lines inside the file beyond what's in the source change: block. End the file with a single trailing newline.
  2. changelogs/current.yaml — rewritten to contain only:

    date: Pending

    (Preserve the date: value from the original file if it isn't Pending — though in practice it always is during dev.)

  3. changelogs/areas.yaml — new file, generated from the set of area: values seen across current.yaml. Format mirrors sections.yaml:

    # NB: areas listed here are the canonical set accepted by per-entry changelog
    #     filenames in changelogs/current/<section>/<area>__<slug>.rst.
    #     In filenames, '/' MUST be encoded as '~'.
    #     Adding a new area requires a PR updating this file.
    access_log:
    build:
    # ...sorted alphabetically

    Areas listed alphabetically, one per line, value empty (allow optional title:/description: later).

    Important: If changelogs/areas.yaml already exists when the script runs, MERGE: keep existing entries, add any new ones discovered in current.yaml, and emit a warning to stderr listing the added areas. Do not silently overwrite a hand-edited file.

CLI

python tools/changelogs/migrate_to_per_entry.py [--project-path PATH] [--dry-run] [--force]
  • --project-path defaults to the repo root (auto-detect by walking up from script location until changelogs/current.yaml is found).
  • --dry-run prints what would be written without touching the filesystem. List each output file with byte count and the first line of content.
  • --force overwrites existing changelogs/current/ directory. Without --force, refuse to run if changelogs/current/ already exists and is non-empty.
  • Exit nonzero on any validation error (unknown section in current.yaml, malformed entry missing area: or change:, etc.).

Validation the script must perform before writing anything

  • Every top-level key in current.yaml (other than date) is present in sections.yaml.
  • Every entry has both area: (string) and change: (string) fields.
  • area: does not contain ~ (would clash with the encoding) — fail with a clear error if so.
  • area: does not contain __ (would clash with the separator).
  • Generated slugs are non-empty.

If any validation fails, print all failures (not just the first) and exit nonzero without writing anything.

Tests

Add tools/changelogs/migrate_to_per_entry_test.py (pytest) covering:

  • Round-trip: build a synthetic current.yaml with a couple of sections and entries (including one with a nested area like extensions/filters/http/oauth2), run ...

This pull request was created from Copilot chat.

@repokitteh-read-only
Copy link
Copy Markdown

As a reminder, PRs marked as draft will not be automatically assigned reviewers,
or be handled by maintainer-oncall triage.

Please mark your PR as ready when you want it to be reviewed!

🐱

Caused by: #45092 was opened by Copilot.

see: more, trace.

Copilot AI changed the title [WIP] Add conversion script for changelog migration Add one-shot changelog migration script for per-entry layout May 15, 2026
Copilot finished work on behalf of phlax May 15, 2026 18:57
Copilot AI requested a review from phlax May 15, 2026 18:57
@phlax phlax force-pushed the copilot/add-conversion-script-for-changelog branch from 6a9c64b to d027405 Compare May 18, 2026 18:08
Post-migration toolshed expects a single `changelogs/changelogs.yaml`
config file containing both `sections:` and `areas:` keys, rather than
separate `sections.yaml` and `areas.yaml` files. Update the migration
script to:

- Read existing `changelogs/sections.yaml` and fold its content under
  `sections:` in the new `changelogs/changelogs.yaml`.
- Place the discovered areas under `areas:` in the same file.
- Remove the now-redundant `changelogs/sections.yaml` after writing.

See: envoy.base.utils.abstract.project.changelog.CHANGELOG_CONFIG_PATH
(= `changelogs/changelogs.yaml`) in envoyproxy/toolshed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants