Skip to content

Instantly share code, notes, and snippets.

@manics
Last active August 4, 2025 18:19
Show Gist options
  • Save manics/61e7edb75f0f35bd12c507b79a6e0110 to your computer and use it in GitHub Desktop.
Save manics/61e7edb75f0f35bd12c507b79a6e0110 to your computer and use it in GitHub Desktop.
Extract relevant sections of a CHANGELOG for a given version
#!/usr/bin/env python
from argparse import ArgumentParser
from base64 import b64decode
from github import Github, GithubException
import os
import re
# If None then unauthenticated requests are made (may hit API limits)
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
parser = ArgumentParser()
parser.add_argument("repo")
parser.add_argument("tag")
args = parser.parse_args()
repo = Github(GITHUB_TOKEN).get_repo(args.repo)
version = args.tag.lstrip("v")
changelog_paths = [
"docs/source/reference/changelog.md",
"docs/source/reference/CHANGELOG.md",
"docs/source/changelog.md",
"docs/source/CHANGELOG.md",
"changelog.md",
"CHANGELOG.md",
]
c = None
for p in changelog_paths:
try:
c = repo.get_contents(p, args.tag)
break
except GithubException:
pass
if not c:
raise Exception("Failed to find changelog")
text = b64decode(c.content).decode().splitlines()
pattern = re.compile(
r"""
^([\#]+)\s+ # Heading
\[? # Optional [
v? # Optional v
"""
+ re.escape(version)
+ r"""
\]? # Optional ]
(\s+|$) # Space or EOL
""",
re.VERBOSE,
)
tag_notes = []
heading_level = None
heading_re = re.compile(r"^([\#]+)\s+")
for line in text:
if heading_level:
m = heading_re.match(line)
if m and len(m.group(1)) <= heading_level:
break
tag_notes.append(line)
else:
m = pattern.match(line)
if m:
heading_level = len(m.group(1))
next_heading_re = re.compile(r"^" + re.escape(m.group(1)) + r"\s+")
tag_notes.append(line)
if not tag_notes:
raise Exception("Failed to find version in changelog")
print("\n".join(tag_notes))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment