Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions .github/publish-git-tag.sh
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
#!/usr/bin/env bash
# Tag the commit that published this version.
# Tag the commit that published this version, and create its GitHub release.
#
# Run from the publish job after the version bump has landed on main. Exits
# non-zero if tagging fails, so a broken release is visible rather than silent.
# non-zero if either step fails, so a broken release is visible rather than
# silent. Both steps are independently idempotent: an existing tag does not
# stop the release from being created, so a rerun after a partial failure
# finishes the job rather than passing with nothing done.
set -euo pipefail

PYTHON=$(command -v python || command -v python3)
VERSION=$("$PYTHON" -c "import re, pathlib; print(re.search(r'^version\s*=\s*\"(\d+\.\d+\.\d+)\"', pathlib.Path('pyproject.toml').read_text(), re.M).group(1))")
TAG="v${VERSION}"

if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
echo "Tag ${TAG} already exists locally; nothing to do."
exit 0
# The remote is the only state that matters here: gh release create
# --verify-tag reads the tag from the remote, so a tag that exists only
# locally must still be pushed. Decide on the remote, then make the local
# tag match and push it.
if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
echo "Tag ${TAG} already exists on the remote."
else
echo "Tagging ${TAG}"
# Create it only if it is not already here; git tag fails on an existing name.
git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null || git tag "${TAG}"
git push origin "${TAG}"
fi

if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
echo "Tag ${TAG} already exists on the remote; nothing to do."
# Zenodo archives on the GitHub release, not on the tag, so a tag alone leaves
# the DOI pointing at whatever was last released by hand.
if gh release view "${TAG}" >/dev/null 2>&1; then
echo "Release ${TAG} already exists."
exit 0
fi

echo "Tagging ${TAG}"
git tag "${TAG}"
git push origin "${TAG}"
# --verify-tag so gh aborts if the tag is missing from the remote, rather than
# creating one itself at the default branch head.
echo "Creating release ${TAG}"
NOTES=$("$PYTHON" .github/release_notes.py "${VERSION}" 2>/dev/null || true)
if [ -n "${NOTES}" ]; then
printf '%s\n' "${NOTES}" | gh release create "${TAG}" --title "${TAG}" --verify-tag --notes-file -
else
gh release create "${TAG}" --title "${TAG}" --verify-tag --generate-notes
fi
23 changes: 23 additions & 0 deletions .github/release_notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Print the CHANGELOG section for one version, for use as release notes.

towncrier writes the curated entry; `gh release create --generate-notes` would
write a raw commit list instead. Prints nothing if the section is absent, and
the caller falls back to generated notes.
"""

import re
import sys
from pathlib import Path


def section_for(changelog: str, version: str) -> str:
pattern = rf"^## \[{re.escape(version)}\][^\n]*\n(.*?)(?=^## \[|\Z)"
match = re.search(pattern, changelog, re.M | re.S)
return match.group(1).strip() if match else ""


if __name__ == "__main__":
path = Path("CHANGELOG.md")
if not path.exists():
sys.exit(0)
print(section_for(path.read_text(), sys.argv[1]))
4 changes: 3 additions & 1 deletion .github/workflows/versioning.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,7 @@ jobs:
skip-existing: true
# After the upload, so a tag failure can never hold back a release.
# skip-existing above keeps a rerun safe.
- name: Publish a git tag
- name: Publish a git tag and GitHub release
run: ".github/publish-git-tag.sh"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions changelog.d/auto-release.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Releases are now created automatically from the tag, so Zenodo archives every version rather than only those released by hand.
Loading