Skip to content

ci: release from annotated tags; remove CHANGELOG-archive.md - #1588

Open
dimitri-yatsenko wants to merge 4 commits into
masterfrom
docs/remove-changelog-archive
Open

dimitri-yatsenko wants to merge 4 commits into
masterfrom
docs/remove-changelog-archive

Conversation

@dimitri-yatsenko

@dimitri-yatsenko dimitri-yatsenko commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Releases become annotated tags whose messages are the release notes, and the changelog file is removed. The history lives in the tags' messages; the GitHub releases mirror them.

Releases from annotated tags

A release is an annotated tag vX.Y.Z on master, created with git tag -a vX.Y.Z --cleanup=verbatim -F notes.md. Pushing it runs the new Release workflow (.github/workflows/release.yaml):

  1. Check the tag (.github/scripts/check_release_tag.sh). It fetches the tag object itself, because checkout can store an annotated tag as a lightweight ref.
    • It fails on a lightweight tag, and the error gives the git tag -a command.
    • It fails on a tag whose commit is not on master.
    • It stops, without publishing, on a tag whose message has a Retroactive: line. Such a tag records a past release.
  2. Run the lint and test workflows as the gate. They gain a workflow_call trigger for this.
  3. Build the package at the tag. hatch-vcs takes the version from the tag, and a step checks that the wheel and sdist carry it.
  4. Publish with twine, using the same secrets as before.
  5. Create the GitHub release: gh release create "$TAG" --verify-tag --notes-file <the tag's message> dist/*. This replaces the archived upload-release-asset action.
  6. Post to Slack, as before.

Running the workflow by hand from a tag publishes it to TestPyPI. With testpypi unchecked, it repeats a release that failed partway.

This replaces release-drafter: the Manual Draft Release workflow, release_drafter.yaml, and the workflow that published a release once its draft was published. Labels no longer produce the notes, but a pull request's title must still state its user-visible change, because the notes are written from the titles. RELEASE_MEMO.md describes how to cut a release, and CONTRIBUTING.md points to it.

The history in the tags

Every past release that has notes has been re-tagged as an annotated tag on the same commit, with those notes as its message:

  • 0.3.2–0.14.3 take their section of CHANGELOG-archive.md (47 versions);
  • later versions take the GitHub release body (24 versions);
  • v0.11.2 also takes its GitHub release body. The archive's 0.11.2 entry (the pyparsing fix of Jul 25, 2019) shipped as 0.11.3, while the v0.11.2 tag marks the build published to PyPI on 2019-04-13, and its release says so.

A version with no notes anywhere keeps its tag as it is. That applies to v0.4.8, v0.6.1, v0.7.1, v0.8.1, v0.8.2 and v0.10.2.

A re-tagged message ends like this:

Released: 2024-09-23
Retroactive: re-tagged 2026-10-20 from CHANGELOG-archive.md at a00b89f5544e (was a lightweight tag)

No tag moved to another commit, so pip install git+...@vX.Y.Z, the versions hatch-vcs derives, and the GitHub releases are unaffected.

If your clone already has these tags, a plain git fetch --tags reports "would clobber existing tag" for them. Run git fetch --force --tags once.

With the history in the tags, CHANGELOG-archive.md is removed. Nothing else refers to it or to release-drafter.

Checks

  • actionlint, with shellcheck, passes on the new and changed workflows. shellcheck passes on the check script.

  • The check script was run on a scratch repository against five tags:

    Tag Result
    annotated, on master publish
    lightweight fail, with the git tag -a command
    retroactive stop without publishing
    annotated, off master fail
    not vX.Y.Z fail

    A clone that stores the annotated tag as a lightweight ref is still read as annotated.

Supersedes #1609.

@dimitri-yatsenko dimitri-yatsenko added the documentation Issues related to documentation label Oct 15, 2026
Release history now lives entirely in git tags and GitHub Releases.
Every version the archive covered (0.3.2 through 0.14.3) has a tag
and a release carrying its notes.
A release is now an annotated tag vX.Y.Z on master whose message is the release notes. Pushing the tag runs the new Release workflow: it checks the tag, runs the lint and test workflows as the gate, builds the package at the tag, publishes it to PyPI, creates the GitHub release with the tag's message as its notes and the wheel and sdist attached, and posts to Slack.

The check (`.github/scripts/check_release_tag.sh`) fetches the tag object itself, since checkout can store an annotated tag as a lightweight ref. It fails on a lightweight tag, naming the `git tag -a` command, and on a tag whose commit is not on master. It stops without publishing on a tag whose message has a `Retroactive:` line, which marks a past release re-tagged with its notes. Running the workflow by hand from a tag publishes it to TestPyPI, or, with testpypi unchecked, repeats a failed release.

This replaces release-drafter: the Manual Draft Release workflow, its configuration, and the workflow that published a release once its draft was published, including the archived upload-release-asset action. The lint and test workflows gain a workflow_call trigger so the release can run them. RELEASE_MEMO.md describes how a release is cut, and CONTRIBUTING.md points to it. Pull request titles still state the user-visible change, since the notes are written from them; labels no longer drive the notes.
@dimitri-yatsenko
dimitri-yatsenko force-pushed the docs/remove-changelog-archive branch from 86e3038 to 4d2b8fe Compare October 20, 2026 22:41
@dimitri-yatsenko dimitri-yatsenko added enhancement Indicates new improvements and removed documentation Issues related to documentation labels Oct 20, 2026
@dimitri-yatsenko dimitri-yatsenko changed the title docs: remove CHANGELOG-archive.md; history lives in tags and releases ci: release from annotated tags; remove CHANGELOG-archive.md Oct 20, 2026
@dimitri-yatsenko
dimitri-yatsenko marked this pull request as draft October 21, 2026 16:46

@MilagrosMarin MilagrosMarin 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.

Thanks @dimitri-yatsenko, this is a clean design: tags as the single record, a gate that refuses lightweight tags, and the retroactive history preserved. I checked that the past tags (e.g. v2.0.0, v0.14.3, v0.11.2) are already annotated on the remote, and CI is green.

Releasing from the GitHub UI
The web UI can't create an annotated tag. "Draft a new release → Create new tag on publish" makes a lightweight tag, so a release done the usual UI way would:

  1. create the GitHub release and the tag,
  2. fail check_release_tag.sh (lightweight), so nothing reaches PyPI,
  3. need the CLI to fix, and then gh release create fails because the release already exists.

Could we support a UI path? One option:

  • Maintainer writes the notes in a draft release (Save draft, which doesn't create the tag).
  • A workflow_dispatch "Tag release" workflow (input: version) reads the draft body and creates the annotated tag via the API.
  • Since a tag pushed with GITHUB_TOKEN won't trigger release.yaml, either call it directly (workflow_call) or use an App token.
  • release.yaml publishes the existing draft (gh release edit --draft=false + upload assets) instead of gh release create.

If that's out of scope, a line in RELEASE_MEMO.md saying "don't create the tag from the Releases page; it makes a lightweight tag" would prevent a confusing first failure.

Minor

  • Re-runs: if a step after gh release create fails (e.g. Slack), re-running fails at create. Skipping when the release exists would keep re-runs idempotent.
  • Since pushing v* now publishes to PyPI, a tag ruleset restricting who can create v* tags may be worth adding if one isn't in place.
  • RELEASE_MEMO.md says never delete a pushed tag, while the lightweight-tag error says to delete and force-push. Worth noting that a tag rejected by the check is the exception.

Let me know what you think!

@MilagrosMarin

Copy link
Copy Markdown
Contributor

One more small thing on the release flow: step 3 leaves a notes.md behind on the releaser's machine, and nothing stops it from being committed by accident. Two light options:

  • Tag without a file: git tag -a vX.Y.Z --cleanup=verbatim -e (opens the editor; -F - reads stdin).
  • Add notes.md to .gitignore.

The draft-release workflow suggested above would remove the local file entirely, since the notes would live in the draft.

@dimitri-yatsenko dimitri-yatsenko modified the milestones: v2.3.6, v2.3.7 Nov 6, 2026
Write the release notes to git tag on standard input (-F -) so no notes
file is left in the working tree. Note that tags created from the
Releases page are lightweight and refused, and that a tag refused before
publishing is the one tag that may be replaced. The release step updates
an existing GitHub release instead of failing on re-run.
@dimitri-yatsenko

Copy link
Copy Markdown
Member Author

Fixed in 00bff96: step 3 now passes the notes to git tag -a vX.Y.Z --cleanup=verbatim -F - from a quoted heredoc, so no notes.md is left in the working tree. I went with stdin rather than -e, since with --cleanup=verbatim the editor's own # instructions would stay in the tag message; the memo says so. The lightweight-tag error in check_release_tag.sh shows the same stdin form.

@dimitri-yatsenko

Copy link
Copy Markdown
Member Author

Thanks @MilagrosMarin. Fixed in 00bff96:

  • Releasing from the GitHub UI: RELEASE_MEMO.md now says to create the tag with git, not from the Releases page, since "Create new tag on publish" makes a lightweight tag that the workflow refuses.
  • Re-runs: the release step updates an existing GitHub release (gh release edit --draft=false plus gh release upload --clobber) instead of failing at gh release create. This also covers your step 3: after the lightweight tag is replaced and the workflow re-run, it updates the release that the UI created.
  • Deleting tags: the memo now limits "never move or delete" to tags the workflow has published, and names a tag it refused before publishing anything (such as a lightweight one) as the exception, which matches the error message.

@dimitri-yatsenko

Copy link
Copy Markdown
Member Author

@MilagrosMarin the browser release path you proposed is now #1680, as a follow-up so this PR stays focused on tag-based releases. The tag ruleset is a repository setting and will be handled separately.

This branch has not been deployed

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

Labels

enhancement Indicates new improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants