Skip to content

Commit 6365379

Browse files
Implement automatic changelog generation for PRs
Co-authored-by: saulshanabrook <[email protected]>
1 parent d321e3e commit 6365379

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed

.github/scripts/update_changelog.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script to automatically update the changelog with PR information.
4+
"""
5+
6+
import argparse
7+
import re
8+
import sys
9+
from pathlib import Path
10+
11+
12+
def find_unreleased_section(lines):
13+
"""Find the line number where UNRELEASED section starts and ends."""
14+
unreleased_start = None
15+
content_start = None
16+
17+
for i, line in enumerate(lines):
18+
if line.strip() == "## UNRELEASED":
19+
unreleased_start = i
20+
continue
21+
22+
if unreleased_start is not None and content_start is None:
23+
# Skip empty lines after ## UNRELEASED
24+
if line.strip() == "":
25+
continue
26+
else:
27+
content_start = i
28+
break
29+
30+
return unreleased_start, content_start
31+
32+
33+
def update_changelog(changelog_path, pr_number, pr_title, pr_url):
34+
"""Update the changelog with the new PR entry."""
35+
36+
# Read the current changelog
37+
with open(changelog_path, 'r', encoding='utf-8') as f:
38+
lines = f.readlines()
39+
40+
# Find the UNRELEASED section
41+
unreleased_start, content_start = find_unreleased_section(lines)
42+
43+
if unreleased_start is None:
44+
print("ERROR: Could not find '## UNRELEASED' section in changelog")
45+
return False
46+
47+
if content_start is None:
48+
print("ERROR: Could not find content start after UNRELEASED section")
49+
return False
50+
51+
# Create the new entry
52+
new_entry = f"- {pr_title} [#{pr_number}]({pr_url})\n"
53+
54+
# Check if this PR entry already exists to avoid duplicates
55+
for line in lines[content_start:]:
56+
if f"[#{pr_number}]" in line:
57+
print(f"Changelog entry for PR #{pr_number} already exists")
58+
return False
59+
# Stop checking when we reach the next section
60+
if line.startswith("## ") and not line.strip() == "## UNRELEASED":
61+
break
62+
63+
# Insert the new entry at the beginning of the unreleased content
64+
lines.insert(content_start, new_entry)
65+
66+
# Write the updated changelog
67+
with open(changelog_path, 'w', encoding='utf-8') as f:
68+
f.writelines(lines)
69+
70+
print(f"Added changelog entry for PR #{pr_number}: {pr_title}")
71+
return True
72+
73+
74+
def main():
75+
parser = argparse.ArgumentParser(description='Update changelog with PR information')
76+
parser.add_argument('--pr-number', required=True, help='Pull request number')
77+
parser.add_argument('--pr-title', required=True, help='Pull request title')
78+
parser.add_argument('--pr-url', required=True, help='Pull request URL')
79+
parser.add_argument('--changelog-path', default='docs/changelog.md', help='Path to changelog file')
80+
81+
args = parser.parse_args()
82+
83+
changelog_path = Path(args.changelog_path)
84+
85+
if not changelog_path.exists():
86+
print(f"ERROR: Changelog file not found: {changelog_path}")
87+
sys.exit(1)
88+
89+
success = update_changelog(changelog_path, args.pr_number, args.pr_title, args.pr_url)
90+
91+
if not success:
92+
sys.exit(1)
93+
94+
95+
if __name__ == '__main__':
96+
main()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Update Changelog
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited]
6+
7+
jobs:
8+
update-changelog:
9+
# Only run if this is not a PR from a fork to avoid permission issues
10+
# and not a commit made by GitHub Action to avoid infinite loops
11+
if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'github-actions[bot]'
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: write
15+
pull-requests: write
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
with:
21+
# Checkout the PR head ref
22+
ref: ${{ github.event.pull_request.head.ref }}
23+
token: ${{ secrets.GITHUB_TOKEN }}
24+
25+
- name: Set up Python
26+
uses: actions/setup-python@v5
27+
with:
28+
python-version: '3.12'
29+
30+
- name: Update changelog
31+
run: |
32+
python .github/scripts/update_changelog.py \
33+
--pr-number="${{ github.event.pull_request.number }}" \
34+
--pr-title="${{ github.event.pull_request.title }}" \
35+
--pr-url="${{ github.event.pull_request.html_url }}"
36+
37+
- name: Check for changes
38+
id: changes
39+
run: |
40+
if git diff --quiet docs/changelog.md; then
41+
echo "changed=false" >> $GITHUB_OUTPUT
42+
else
43+
echo "changed=true" >> $GITHUB_OUTPUT
44+
fi
45+
46+
- name: Commit and push changes
47+
if: steps.changes.outputs.changed == 'true'
48+
run: |
49+
git config --local user.email "[email protected]"
50+
git config --local user.name "GitHub Action"
51+
git add docs/changelog.md
52+
git commit -m "Add changelog entry for PR #${{ github.event.pull_request.number }}"
53+
git push

docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ _This project uses semantic versioning_
44

55
## UNRELEASED
66

7+
- Automatically Generate Changelog Entries for PRs [#312](https://github.com/egraphs-good/egglog-python/pull/312)
78
- Upgrade egglog which includes new backend.
89
- Fixes implementation of the Python Object sort to work with objects with dupliating hashes but the same value.
910
Also changes the representation to be an index into a list instead of the ID, making egglog programs more deterministic.

0 commit comments

Comments
 (0)