Add automated release notes generation workflow #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Update Release Notes | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| types: [opened, synchronize, reopened] | |
| jobs: | |
| update-release-notes: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Fetch all history for proper changelog generation | |
| ref: ${{ github.head_ref || github.ref_name }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.11' | |
| - name: Generate release notes | |
| run: | | |
| # Get the version from setup.py | |
| VERSION=$(python setup.py --version 2>/dev/null || echo "dev") | |
| # Determine the commit range | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| BASE_REF="${{ github.event.pull_request.base.sha }}" | |
| HEAD_REF="${{ github.event.pull_request.head.sha }}" | |
| COMMIT_RANGE="${BASE_REF}..${HEAD_REF}" | |
| else | |
| # For push events, use the last 20 commits | |
| COMMIT_RANGE="HEAD~20..HEAD" | |
| fi | |
| # Create a temporary file for the new release notes | |
| cat > /tmp/new_release_section.md << 'EOF' | |
| # v${VERSION} | |
| This release includes updates from recent commits. | |
| --- | |
| ## 🚀 New Features | |
| EOF | |
| # Process commits and categorize them | |
| echo "Processing commits in range: ${COMMIT_RANGE}" | |
| # Extract feature commits | |
| git log ${COMMIT_RANGE} --date=short --pretty=format:"* %ad %s (%aN)" --grep="^feature:" --grep="^feat:" -i > /tmp/features.txt || true | |
| if [ -s /tmp/features.txt ]; then | |
| cat /tmp/features.txt >> /tmp/new_release_section.md | |
| else | |
| echo "* No new features in this update" >> /tmp/new_release_section.md | |
| fi | |
| echo "" >> /tmp/new_release_section.md | |
| echo "---" >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| echo "## 💗 Other Tag Highlights" >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| echo "* 🔁 **Tests**" >> /tmp/new_release_section.md | |
| # Extract test commits | |
| git log ${COMMIT_RANGE} --date=short --pretty=format:" * %ad %s (%aN)" --grep="^test:" -i > /tmp/tests.txt || true | |
| if [ -s /tmp/tests.txt ]; then | |
| cat /tmp/tests.txt >> /tmp/new_release_section.md | |
| else | |
| echo " * No test updates" >> /tmp/new_release_section.md | |
| fi | |
| echo "" >> /tmp/new_release_section.md | |
| echo "* 📚 **Documentation**" >> /tmp/new_release_section.md | |
| # Extract doc commits | |
| git log ${COMMIT_RANGE} --date=short --pretty=format:" * %ad %s (%aN)" --grep="^docs:" -i > /tmp/docs.txt || true | |
| if [ -s /tmp/docs.txt ]; then | |
| cat /tmp/docs.txt >> /tmp/new_release_section.md | |
| else | |
| echo " * No documentation updates" >> /tmp/new_release_section.md | |
| fi | |
| echo "" >> /tmp/new_release_section.md | |
| echo "---" >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| echo "## 🐛 **Bugfixes**" >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| # Extract bugfix commits | |
| git log ${COMMIT_RANGE} --date=short --pretty=format:"* %ad %s (%aN)" --grep="^fix:" --grep="^bugfix:" -i > /tmp/fixes.txt || true | |
| if [ -s /tmp/fixes.txt ]; then | |
| cat /tmp/fixes.txt >> /tmp/new_release_section.md | |
| else | |
| echo "* No bugfixes in this update" >> /tmp/new_release_section.md | |
| fi | |
| echo "" >> /tmp/new_release_section.md | |
| echo "---" >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| echo "## 📝 **Full changelog**" >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| # Count commits by category | |
| TOTAL_COMMITS=$(git log ${COMMIT_RANGE} --oneline | wc -l) | |
| FEAT_COUNT=$(git log ${COMMIT_RANGE} --oneline --grep="^feature:" --grep="^feat:" -i | wc -l || echo 0) | |
| TEST_COUNT=$(git log ${COMMIT_RANGE} --oneline --grep="^test:" -i | wc -l || echo 0) | |
| DOCS_COUNT=$(git log ${COMMIT_RANGE} --oneline --grep="^docs:" -i | wc -l || echo 0) | |
| FIX_COUNT=$(git log ${COMMIT_RANGE} --oneline --grep="^fix:" --grep="^bugfix:" -i | wc -l || echo 0) | |
| STYLE_COUNT=$(git log ${COMMIT_RANGE} --oneline --grep="^style:" -i | wc -l || echo 0) | |
| if [ ${TOTAL_COMMITS} -gt 0 ]; then | |
| FEAT_PCT=$(awk "BEGIN {printf \"%.1f\", (${FEAT_COUNT}/${TOTAL_COMMITS})*100}") | |
| TEST_PCT=$(awk "BEGIN {printf \"%.1f\", (${TEST_COUNT}/${TOTAL_COMMITS})*100}") | |
| DOCS_PCT=$(awk "BEGIN {printf \"%.1f\", (${DOCS_COUNT}/${TOTAL_COMMITS})*100}") | |
| FIX_PCT=$(awk "BEGIN {printf \"%.1f\", (${FIX_COUNT}/${TOTAL_COMMITS})*100}") | |
| STYLE_PCT=$(awk "BEGIN {printf \"%.1f\", (${STYLE_COUNT}/${TOTAL_COMMITS})*100}") | |
| OTHER_COUNT=$((TOTAL_COMMITS - FEAT_COUNT - TEST_COUNT - DOCS_COUNT - FIX_COUNT - STYLE_COUNT)) | |
| OTHER_PCT=$(awk "BEGIN {printf \"%.1f\", (${OTHER_COUNT}/${TOTAL_COMMITS})*100}") | |
| else | |
| FEAT_PCT=0 | |
| TEST_PCT=0 | |
| DOCS_PCT=0 | |
| FIX_PCT=0 | |
| STYLE_PCT=0 | |
| OTHER_PCT=0 | |
| fi | |
| cat >> /tmp/new_release_section.md << EOF | |
| | **${TOTAL_COMMITS} commits** | 📚 Docs | 🧪 Tests | 🐛 Fixes | 🎨 Style | ✨ Features | Other | | |
| |-----------------|---------|----------|-----------|------------|--------------|-------| | |
| | % of Commits | ${DOCS_PCT}% | ${TEST_PCT}% | ${FIX_PCT}% | ${STYLE_PCT}% | ${FEAT_PCT}% | ${OTHER_PCT}% | | |
| EOF | |
| echo "" >> /tmp/new_release_section.md | |
| echo '`git log --date=short --pretty=format:"* %ad %s (%aN)"`' >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| # Add all commits | |
| git log ${COMMIT_RANGE} --date=short --pretty=format:"* %ad %s (%aN)" >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| echo "" >> /tmp/new_release_section.md | |
| # Prepend the new section to the existing release.md | |
| if [ -f release.md ]; then | |
| cat /tmp/new_release_section.md > /tmp/updated_release.md | |
| echo "---" >> /tmp/updated_release.md | |
| echo "" >> /tmp/updated_release.md | |
| cat release.md >> /tmp/updated_release.md | |
| mv /tmp/updated_release.md release.md | |
| else | |
| mv /tmp/new_release_section.md release.md | |
| fi | |
| # Replace ${VERSION} placeholder | |
| sed -i "s/\${VERSION}/${VERSION}/g" release.md | |
| - name: Check for changes | |
| id: check_changes | |
| run: | | |
| if git diff --quiet release.md; then | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "No changes to release.md" | |
| else | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| echo "Changes detected in release.md" | |
| fi | |
| - name: Commit and push changes | |
| if: steps.check_changes.outputs.has_changes == 'true' && github.event_name == 'push' | |
| run: | | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --local user.name "github-actions[bot]" | |
| git add release.md | |
| git commit -m "chore: auto-update release.md [skip ci]" | |
| git push | |
| - name: Commit changes to PR | |
| if: steps.check_changes.outputs.has_changes == 'true' && github.event_name == 'pull_request' | |
| run: | | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --local user.name "github-actions[bot]" | |
| git add release.md | |
| git commit -m "chore: auto-update release.md for PR #${{ github.event.pull_request.number }}" | |
| git push origin HEAD:${{ github.head_ref }} |