-
-
Save acjackman/2feaf43359faaa5046fc67a131b3a9ed to your computer and use it in GitHub Desktop.
Generate release notes from git commit messages
This file contains 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
#!/usr/bin/python | |
# A script to automate changelog generation from Git commit messages | |
# | |
# For use with a git-flow workflow, it will take changes from the last tagged release | |
# where commit messages contain NEW, FIXED, and IMPROVED keywords and sort and fromat | |
# them into a Markdown release note list. | |
# | |
# Forked from https://gist.github.com/ttscoff/17fbce4f229609082b45681bba7a9967 | |
from git import Repo | |
import re | |
from collections import OrderedDict | |
class LogEntry: | |
def __init__(self, log_hash, date, title, msg_lines, *args, **kwargs): | |
super(LogEntry, self).__init__(*args, **kwargs) | |
self.log_hash = log_hash | |
self.date = date | |
self.title = title | |
self.msg_lines = msg_lines | |
def __str__(self): | |
return self.log_hash | |
class ChangeLoger: | |
def __init__(self, repo, *args, **kwargs): | |
super(ChangeLoger, self).__init__(*args, **kwargs) | |
sections = OrderedDict([ | |
('new', {'title': "NEW", 'rx': r'(NEW|ADD(ED)?)'}), | |
('improved', {'title': "IMPROVED", 'rx': r'IMPROV(MENT|ED)?'}), | |
('fixed', {'title': "FIXED", 'rx': r'FIX(ED)?'}), | |
]) | |
for k, v in sections.items(): | |
sections[k]['rx'] = re.compile( | |
r'^([\s]*(-)?[\s]*)' + | |
sections[k]['rx'] + | |
r'[\s]*:?[\s]*(?P<msg>.*)' | |
) | |
self.sections = sections | |
log_entries = self.gitlog(repo) | |
self.changes = self.find_changes(log_entries) | |
def gitlog(self, repo): | |
git = repo.git | |
last_tag_hash = git.rev_list(tags=True, max_count=1) | |
tag_time = git.show(last_tag_hash, format='%ad', s=True) | |
log = git.log(pretty="format:===%h%n%ci%n%s%n%b", since=tag_time) | |
if log and len(log) > 0: | |
log_entries = [ | |
ChangeLoger.split_gitlog(entry.strip()) | |
for entry in log.split('===') | |
if entry.strip() != "" | |
] | |
return log_entries | |
raise Exception("error reading log items") | |
@staticmethod | |
def split_gitlog(entry): | |
'''Split a raw log entry into a clean Change object''' | |
lines = entry.split("\n") | |
log_hash = lines.pop(0) | |
date = lines.pop(0) | |
title = lines.pop(0) | |
changes = [l.strip() for l in lines if not l.isspace()] | |
return LogEntry(log_hash, date, title, changes) | |
def find_changes(self, log_entries): | |
'''Split the log_entries into changes''' | |
changes = {k: [] for k, v in self.sections.items()} | |
msg_lines = [line for entry in log_entries for line in entry.msg_lines] | |
for line in msg_lines: | |
for k, v in self.sections.items(): | |
match = v['rx'].match(line) | |
if match: | |
changes[k].append(match.group('msg')) | |
return changes | |
def __str__(self): | |
'''Build String representation''' | |
output = '' | |
for key, changes in self.changes.items(): | |
if len(changes) > 0: | |
output = '{old_output}#### {title}\n{changes}\n'.format( | |
old_output=output, | |
title=self.sections[key]['title'], | |
changes='\n'.join(['- ' + line for line in changes]) | |
) | |
if output == '': | |
output = "Cool changes under the hood!" | |
return output | |
if __name__ == "__main__": | |
cl = ChangeLoger(Repo()) | |
print(cl) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment