Skip to content

Instantly share code, notes, and snippets.

@acjackman
Forked from ttscoff/changelog.rb
Last active October 11, 2017 05:13
Show Gist options
  • Save acjackman/2feaf43359faaa5046fc67a131b3a9ed to your computer and use it in GitHub Desktop.
Save acjackman/2feaf43359faaa5046fc67a131b3a9ed to your computer and use it in GitHub Desktop.
Generate release notes from git commit messages
#!/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