Skip to content

Add changelog generation tool for GitHub milestones - #13063

Open
cmcfarlen wants to merge 8 commits into
apache:masterfrom
cmcfarlen:new-changelog-tool
Open

Add changelog generation tool for GitHub milestones#13063
cmcfarlen wants to merge 8 commits into
apache:masterfrom
cmcfarlen:new-changelog-tool

Conversation

@cmcfarlen

Copy link
Copy Markdown
Contributor

Replaces tools/git/changelog.pl with a Python implementation that generates changelogs from merged PRs in a milestone using the GitHub API or gh CLI. Default output matches the existing CHANGELOG-* file format. The --doc mode includes merge SHAs, labels, and full PR descriptions to guide AI-assisted release documentation updates. Supports text and YAML output formats.

@cmcfarlen cmcfarlen added this to the 11.0.0 milestone Apr 6, 2026
@cmcfarlen cmcfarlen self-assigned this Apr 6, 2026
@cmcfarlen cmcfarlen added the Tools label Apr 6, 2026
@cmcfarlen

Copy link
Copy Markdown
Contributor Author

hmm, the uv.lock file is making the RAT check mad. Should I remove uv.lock?

@bneradt

bneradt commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

hmm, the uv.lock file is making the RAT check mad. Should I remove uv.lock?

I think it's recommended to add uv.lock to ensure the exact packages are added. Let's add it back. The problem wasn't your patch adding the lock, the problem is the RAT check incorrectly failing on the lock file. I'll update CI to allow it.


Update

#13066

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new Python-based changelog generator under tools/changelog/ to produce CHANGELOG-*-style output from GitHub milestones (via direct REST API calls or the gh CLI), and updates the release-process documentation to use it.

Changes:

  • Add tools/changelog/changelog.py with text/YAML output and an extended --doc mode for richer metadata.
  • Add tools/changelog/pyproject.toml and tools/changelog/uv.lock for dependency management/execution via uv.
  • Update release-process docs to use the new tool (with --use-gh).

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 7 comments.

File Description
tools/changelog/changelog.py Implements milestone PR collection via REST API or gh, plus output formatting.
tools/changelog/pyproject.toml Defines the Python project and console script entry point.
tools/changelog/uv.lock Pins Python dependencies for uv-managed execution.
doc/developer-guide/release-process/index.en.rst Updates the documented release workflow to generate changelogs via the new tool.

Comment thread doc/developer-guide/release-process/index.en.rst
Comment thread doc/developer-guide/release-process/index.en.rst
Comment thread tools/changelog/changelog.py Outdated
Comment thread tools/changelog/changelog.py Outdated
Comment thread tools/changelog/changelog.py
Comment thread tools/changelog/changelog.py Outdated
Comment thread tools/changelog/changelog.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Replaces the legacy Perl-based milestone changelog generator with a new Python tool under tools/changelog/ that can pull merged PRs for a milestone via the GitHub REST API or the gh CLI, and updates the release process docs accordingly.

Changes:

  • Removed tools/git/changelog.pl (Perl implementation).
  • Added a Python-based changelog generator (tools/changelog/changelog.py) with a pyproject.toml + uv.lock for dependency management.
  • Updated release-process documentation to use the new tool.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tools/git/changelog.pl Removes the old Perl changelog generator.
tools/changelog/changelog.py New Python tool for generating milestone changelogs via GitHub API or gh, with optional doc/YAML output.
tools/changelog/pyproject.toml Defines the Python tool project and dependencies.
tools/changelog/uv.lock Locks Python dependencies for reproducible runs via uv.
doc/developer-guide/release-process/index.en.rst Updates release instructions to use the new Python tool.

Comment thread tools/changelog/pyproject.toml
Comment thread doc/developer-guide/release-process/index.en.rst
Comment thread tools/changelog/changelog.py Outdated
Comment thread tools/changelog/changelog.py
Comment thread tools/changelog/changelog.py Outdated
@cmcfarlen

Copy link
Copy Markdown
Contributor Author

[approve ci autest 2]

@bryancall bryancall left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice to see this move off Perl. The default httpx path looks solid: explicit rate-limit checks, raise_for_status, distinct exit codes, and a good unauthenticated-token warning.

One thing I want fixed before merge. In the --use-gh path, the per-PR merge check treats any non-zero gh api .../merge exit as "not merged" and skips the PR (changelog.py L130). That collapses a genuine 404 (really not merged) together with 403 secondary rate limiting, 5xx, and network errors into the same outcome. Since this makes one API call per PR across a whole milestone, secondary rate limiting is exactly the failure to expect, and when it hits you get a silently incomplete release changelog with a zero exit code. The httpx path already does this right in _is_merged (204 vs 404 vs raise_for_status). Please have the gh path distinguish 404 from other failures and error out on the rest instead of silently skipping. Same goes for the --doc detail fetch, which substitutes empty sha/body on failure rather than surfacing it.

Smaller items, not blocking:

  • --doc help and the module docstring say "full commit message" but the code stores the PR body. Fix the wording (or fetch the actual commit message).
  • The -a/--auth token is visible in ps and shell history. Carried over from the old script, but for new code prefer GH_TOKEN only and mark -a as discouraged.
  • pyproject.toml is missing license = "Apache-2.0" that the other tool packages set, and main() is missing a -> None return annotation.

The Copilot notes about milestone state=all and a --format yaml JSON fallback are already handled in the current code: both milestone lookups use state=all, and yaml exits with a clear error when PyYAML is missing.

Copilot AI review requested due to automatic review settings July 29, 2026 00:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@cmcfarlen cmcfarlen moved this to For v10.2.1 in ATS v10.2.x Aug 5, 2026
cmcfarlen and others added 5 commits September 3, 2026 14:09
Replaces tools/git/changelog.pl with a Python implementation
that generates changelogs from merged PRs in a milestone using
the GitHub API or gh CLI. Default output matches the existing
CHANGELOG-* file format. The --doc mode includes merge SHAs,
labels, and full PR descriptions to guide AI-assisted release
documentation updates. Supports text and YAML output formats.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace reference to tools/git/changelog.pl with the new
tools/changelog/changelog.py invocation using uv run.

Co-Authored-By: Claude <noreply@anthropic.com>

fix python formatting
Any non-zero `gh api .../merge` exit was read as "not merged", so 403
secondary rate limiting or a 5xx silently dropped merged PRs from a
release changelog while still exiting 0. That is the failure to expect,
since the check runs once per PR across a whole milestone.

Use --include so the status line separates a real 404 from a transport
or rate-limit error, and exit on anything else. The --doc detail fetch
fails the same way now rather than substituting an empty sha and body.

Also correct the --doc wording, which stores the PR body and not the
commit message; discourage -a, since it exposes the token in ps output
and shell history; and declare the Apache-2.0 license that the sibling
tool packages set.
Copilot AI review requested due to automatic review settings September 3, 2026 19:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The committed uv.lock is tied to an internal package registry and the new tool has a couple of correctness/UX issues called out in review comments that should be resolved before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 4/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread tools/changelog/changelog.py Outdated
Comment thread tools/changelog/changelog.py
The committed uv.lock resolved through an internal Apple package mirror,
so every artifact URL and the registry itself were unreachable for anyone
outside that network. Rewritten to pypi.org and files.pythonhosted.org;
package versions, sizes and sha256 hashes are unchanged, since the mirror
serves the same artifacts.

The --from-git PR-number regex matched "#N" anywhere in the subject, not
the trailing "(#N)" its docstring describes, so an issue reference could
be captured as a PR number and merge unrelated entries in
merge_changelogs(). Now anchored.

_check_rate_limit() reported a rate limit for every 403, but GitHub also
uses 403 for a missing token or insufficient scopes, where that advice is
wrong and hides the cause. It now exits only on a primary limit
(x-ratelimit-remaining: 0), a secondary limit (retry-after) or a 429, and
lets anything else fall through to the raise_for_status() that follows
each call site.
Copilot AI review requested due to automatic review settings September 9, 2026 16:32
@cmcfarlen

Copy link
Copy Markdown
Contributor Author

Rebuilt this branch on current master and addressed the outstanding review feedback.

Why the diff looked absurd. The branch had been rebased backwards at some point — master's history was replayed onto it rather than the other way around, leaving 160 commits that were patch-equivalents of other people's merged PRs and a merge base stuck in April. That's what produced the +275k/946-file view and the conflicts. I cherry-picked the 6 real commits onto current master; the diff is back to what it should be, 5 files.

@bryancall's review (298c1fd37b):

  • The gh path read any non-zero gh api .../merge exit as "not merged", so a 403 or 5xx silently dropped merged PRs from the changelog and still exited 0. It now uses gh api --include and discriminates on the status line: 204 merged, 404 not merged, anything else is fatal. The --doc detail fetch likewise fails loudly instead of substituting an empty sha/body. The httpx path was already correct here, as you noted, so it's unchanged.
  • --doc help/docstring corrected to say "PR body", noting that --from-git yields the commit message body instead.
  • -a/--auth documented as discouraged (visible in ps and shell history) with a pointer to GH_TOKEN.
  • license = "Apache-2.0" added, matching tools/hrw4u and tools/traffic_grapher.

Copilot's follow-up plus one thing it caught that mattered (6c262932cf):

  • uv.lock had been resolved through an internal package mirror, so the registry and every artifact URL were unreachable outside that network. Repointed at pypi.org/files.pythonhosted.org — versions, sizes and sha256 hashes are unchanged, since it's the same artifacts. Keeping the lock committed per @bneradt's earlier note and the RAT fix in Ignore uv.lock in RAT exclusions #13066.
  • The --from-git PR-number regex matched #N anywhere in the subject rather than the trailing (#N) its docstring describes, so Fix issue #12345 in the cache captured 12345 as a PR number and could merge unrelated entries. Now anchored.
  • _check_rate_limit() announced a rate limit for every 403, but GitHub also uses 403 for a missing token or insufficient scopes, where that advice is wrong and hides the cause. It now exits only on a primary limit (x-ratelimit-remaining: 0), a secondary limit (retry-after), or a 429, and lets everything else fall through to the raise_for_status() that follows each call site.

All 15 checks were green on the previous head. Ready for another look.

@cmcfarlen
cmcfarlen requested a review from bryancall September 9, 2026 16:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 6 comments.

Comment thread tools/changelog/changelog.py Outdated
Comment thread tools/changelog/changelog.py
Comment thread tools/changelog/changelog.py Outdated
Comment thread tools/changelog/changelog.py Outdated
Comment thread tools/changelog/pyproject.toml
Comment thread doc/developer-guide/release-process/index.en.rst

@bryancall bryancall left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like there are some Copilot issues

pyproject.toml declares pyyaml as a required dependency, but the code
guarded the import and failed at runtime if it was missing. httpx is
imported unconditionally and is just as much a third-party dependency, so
the guarded path could only be reached by an install that pyproject.toml
does not describe. YAML output is a documented mode, so the dependency is
required: import it like httpx and drop the dead branch.

merge_changelogs() documented deduplication by PR number but only avoided
collisions between the git and milestone sources, not within the git range
itself. A revert and reapply, or the same commit cherry-picked twice,
carries the same trailing "(#N)" and produced two entries for one PR. Now
deduplicated on the git side too, keeping the first occurrence so the
surviving entry is the chronological one.
Copilot AI review requested due to automatic review settings September 9, 2026 19:15
@cmcfarlen

Copy link
Copy Markdown
Contributor Author

@bryancall — went through Copilot's six comments; they're four distinct points (the PyYAML one is repeated three times). Two fixed in 378b586bfe, two declined with reasoning on the threads.

Fixed:

  • PyYAML required vs optional. pyproject.toml declared it required while the code guarded the import and errored at runtime if missing. httpx is imported unconditionally and is equally a third-party dependency, so the guarded path was only reachable from an install pyproject.toml doesn't describe. YAML output is a documented mode, so I made the dependency real rather than optional — an extra would have made --format yaml fail by default in the workflow the docs describe.
  • Dedup within the git range. merge_changelogs() documented dedup by PR number but only deduped across the git/milestone boundary, not within the git range. A revert-and-reapply, or the same commit cherry-picked twice, carries the same trailing (#N) and produced two entries for one PR. Now deduped on the git side too, keeping the first occurrence so the surviving entry is the chronological one.

Declined (detail on the threads): the suggestion to drop the merge check in --doc mode would give merged-ness two determination paths across two transports, for a rate-limit saving that doesn't bite at release scale. And the suggested docs change to the console script doesn't run — there's no [build-system], so uv treats this as a virtual project and the [project.scripts] entry never materializes; tools/traffic_grapher has the same shape.

Verification on this round: the dedup case tested directly (first occurrence kept, labels grafted onto the survivor, milestone extras and unnumbered security commits intact), plus an authenticated end-to-end --from-git --doc --format yaml run against apache/trafficserver that produced parseable YAML with no duplicate numbers. Unauthenticated, the same run still exits with the rate-limit message, so that path is confirmed too.

@cmcfarlen
cmcfarlen requested a review from bryancall September 9, 2026 19:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.

Comment thread doc/developer-guide/release-process/index.en.rst
Comment thread tools/changelog/changelog.py
Comment thread tools/changelog/changelog.py Outdated
Comment thread tools/changelog/pyproject.toml Outdated

@bryancall bryancall left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot left a lot of review comments. Please fix and/or comment on them and mark them as resolved. I will review after. Thank you.

yaml.safe_dump() instead of yaml.dump(), so the output cannot grow Python
object tags if a non-primitive ever reaches the entry dicts.

Removed [project.scripts]. Without a [build-system] table this is a uv
virtual project -- uv.lock records source = { virtual = "." } -- so the
package is never built and the console script it declares is never created:

    $ uv run --project tools/changelog changelog --help
    error: Failed to spawn: `changelog`

The declaration described an interface that does not exist, and reviewers
read it as the supported entry point twice. Documented the actual
invocation in its place rather than adding a build backend for a
single-file in-tree script.

The release guide now names its prerequisites: uv, and for --use-gh an
authenticated gh. It also documents the GH_TOKEN alternative, since an
unauthenticated run exceeds the API rate limit partway through a
release-sized milestone and exits without writing a changelog.
Copilot AI review requested due to automatic review settings September 9, 2026 20:35
@cmcfarlen

Copy link
Copy Markdown
Contributor Author

Latest round of Copilot comments handled in c0a28af23e; all review threads on the PR are now replied to and resolved.

Fixed:

  • yaml.safe_dump() instead of yaml.dump(), so the output cannot grow Python object tags.
  • Dropped [project.scripts]. This closes out the earlier console-script thread too: with no [build-system] table, uv treats this as a virtual project and never builds the package, so the entry point it declared was never created. It described an interface that does not exist and had been read as the supported one twice, so removing it is the honest fix — preferable to adding a build backend for a single-file in-tree script. A comment in its place records the real invocation.
  • The release guide now names its prerequisites (uv, and an authenticated gh for --use-gh) and documents the GH_TOKEN alternative. Worth being blunt there, so I was: a token is not optional, because an unauthenticated run exceeds the rate limit partway through a release-sized milestone and exits without writing a changelog. I hit exactly that while testing.

Declined, with reasoning on the thread: batching the --doc fetches through GraphQL. It would mean a second, structurally different fetch path alongside REST, in a tool that runs a handful of times per release. 10.2.0 was generated at roughly twice a normal release's PR count without approaching the authenticated limit, and if a future release does trip it, the failure is now loud rather than silent.

@bryancall I'll wait for Copilot to have another pass at this before asking you to look again, so you're not reviewing into a moving target.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@cmcfarlen
cmcfarlen requested review from bryancall and a lite review from Copilot September 9, 2026 21:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.

Comment thread tools/changelog/changelog.py
Comment thread tools/changelog/changelog.py
Comment thread tools/changelog/changelog.py
Comment thread tools/changelog/changelog.py
Comment thread tools/changelog/changelog.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: For v10.2.1

Development

Successfully merging this pull request may close these issues.

4 participants