How to Generate Release Notes from Git Commit Messages in Python
This script fetches recent Git commit messages using conventional commit prefixes (feat, fix, etc.), categorizes them, and prints formatted release notes with today's date.
Python code
55 linesimport subprocess
import re
from datetime import datetime
def get_git_log(since_tag="HEAD~10", format_str="%s"):
"""Retrieve commit messages from git log."""
try:
result = subprocess.run(
["git", "log", f"--since={since_tag}", f"--format={format_str}"],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip().split("\n")
except subprocess.CalledProcessError:
return []
def categorize_commit(msg):
"""Categorize commit message by conventional commit prefix."""
patterns = {
"Features": r"^feat(?:\(.*?\))?:",
"Bug Fixes": r"^fix(?:\(.*?\))?:",
"Refactoring": r"^refactor(?:\(.*?\))?:",
"Documentation": r"^docs(?:\(.*?\))?:",
"Chores": r"^chore(?:\(.*?\))?:"
}
for category, pattern in patterns.items():
if re.match(pattern, msg, re.IGNORECASE):
return category
return "Other Changes"
def generate_release_notes():
"""Main function to produce release notes from git history."""
messages = [m for m in get_git_log() if m.strip()]
if not messages:
print("No commit messages found.")
return
categories = {}
for msg in messages:
cat = categorize_commit(msg)
# Remove the prefix for cleaner output
clean_msg = re.sub(r"^(feat|fix|refactor|docs|chore)(\(.*?\))?:\s*", "", msg, flags=re.IGNORECASE).strip()
categories.setdefault(cat, []).append(clean_msg)
print(f"## Release Notes ({datetime.now().strftime('%Y-%m-%d')})\n")
for category, items in categories.items():
if items:
print(f"### {category}")
for item in items:
print(f"- {item}")
print()
if __name__ == "__main__":
generate_release_notes()
Output
## Release Notes (2025-04-05)
### Features
- add user authentication endpoint
- implement dark mode toggle
### Bug Fixes
- fix login redirect loop
- correct typo in welcome message
### Refactoring
- simplify database connection logic
### Other Changes
- update readme with setup instructions
How it works
The script uses subprocess.run to safely execute git log and capture commit messages. A categorize_commit function applies regex patterns based on the Conventional Commits specification (feat, fix, etc.). After stripping the prefix for readability, commits are grouped by category inside a dictionary. Finally, the output is formatted as Markdown with a heading that includes the current date via datetime.now().
Common mistakes
- Running the script outside a Git repository will cause a `CalledProcessError` and return an empty list.
- Misspelled conventional commit prefixes (e.g., 'fixx' instead of 'fix') will fall to 'Other Changes'.
- Forgetting to strip the prefix from the commit message can lead to duplicate text like 'feat: feat: add login'.
Variations
- Use `--since=<tag>` with a git tag name instead of `HEAD~10` to generate notes for a specific release.
- Replace the plain print formatting with output to a markdown file using `pathlib.Path.write_text()`.
Real-world use cases
- Automating changelog generation for a CI/CD pipeline before every deployment.
- Creating a monthly release summary for an open-source project's contributors.
- Feeding structured commit data into a documentation site or an internal wiki.
Sponsored
Keep learning
Related tutorials and quizzes for this topic.