-
-
Save michalgorny/3326b86c7a474ed7a7cd37c04aeef4c4 to your computer and use it in GitHub Desktop.
Generate release notes from git commit messages into Markdown formatted list
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/ruby | |
# 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 one of follows keywords | |
# NEW | |
# FIXED | |
# IMPROVED | |
# RN | |
# and sort and format them into a Markdown release note list. | |
# Constant CL_STRINGS: Strings for section titles and keywords | |
# :title is what is displayed in output | |
# :rx is the regex search to match each type, case sensitive | |
CL_STRINGS = { | |
'new' => { :title => "FEATURES", :rx => "(FEATURE|NEW|ADD(ED)?)" }, | |
'improved' => { :title => "IMPROVEMENTS", :rx => "IMPROV(EMENT|ED)?" }, | |
'fixed' => { :title => "FIXES", :rx => "FIX(ED)?" }, | |
'other' => { :title => "OTHER", :rx => "RN(ED)?" } | |
} | |
class String | |
def cap_first | |
self.sub(/^([a-z])(.*)$/) { $1.upcase << $2 } | |
end | |
def clean_entry | |
rx = "(?:%s)" % CL_STRINGS.map{|k,v| | |
v[:rx] | |
}.join('|') | |
return self.sub(/(- )?#{rx}:? */,'').cap_first | |
end | |
end | |
class Change < Hash | |
attr_accessor :githash, :date, :title, :changes | |
def initialize(githash, date, title, changes) | |
@githash = githash | |
@date = date | |
@title = title | |
@changes = changes | |
self | |
end | |
end | |
class ChangeSet | |
attr_accessor :new, :improved, :fixed, :other | |
def initialize | |
@new = [] | |
@improved = [] | |
@fixed = [] | |
@other = [] | |
end | |
def add(type, change) | |
case type | |
when 'new' | |
@new.push change | |
when 'improved' | |
@improved.push change | |
when 'fixed' | |
@fixed.push change | |
when 'other' | |
@other.push change | |
end | |
end | |
def get(type) | |
case type | |
when 'new' | |
return @new | |
when 'improved' | |
return @improved | |
when 'fixed' | |
return @fixed | |
when 'other' | |
return @other | |
end | |
return nil | |
end | |
end | |
class ChangeLog < Array | |
def initialize(change_array=[]) | |
return change_array | |
end | |
# returns array of :changes values | |
def changes | |
res = [] | |
self.each {|v| | |
chgs = v.changes.strip.split(/\n/) | |
chgs.delete_if {|e| e =~ /^\s*$/ } | |
res = res.concat(chgs) | |
} | |
res | |
end | |
def changes! | |
replace self.changes | |
end | |
end | |
class ChangeLogger | |
attr_reader :changes | |
def initialize | |
@changes = ChangeSet.new | |
@log = gitlog | |
sort_changes | |
end | |
def to_s | |
output = '' | |
res = {} | |
CL_STRINGS.each {|k,v| | |
changes = @changes.get(k) | |
if changes.any? | |
res[k] = @changes.get(k) | |
end | |
} | |
res.each {|k,v| | |
output += "#### #{CL_STRINGS[k][:title]}\n\n" | |
output += "- #{v.join("\n- ")}\n\n" | |
} | |
output | |
end | |
# returns ChangeLog | |
def gitlog | |
log = %x{git log \ | |
--pretty=format:'===%h%n%ci%n%s%n%b'\ | |
--since="$(git show -s --format=%ad $(git rev-list --tags --max-count=1))"}.strip | |
if (log && log.length > 0) | |
cl = ChangeLog.new | |
log.split(/^===/).each {|entry| | |
e = split_gitlog(entry.strip) | |
cl.push(e) if e.githash | |
} | |
return cl | |
end | |
raise "Error reading log items" | |
end | |
def split_gitlog(entry) | |
lines = entry.split(/\n/) | |
loghash = lines.shift | |
date = lines.shift | |
title = lines.shift | |
changes = lines.delete_if {|l| l.strip.empty? }.join("\n") | |
return Change.new(loghash, date, title, changes) | |
end | |
def sort_changes | |
rx = "(%s)" % CL_STRINGS.map {|k,v| v[:rx]}.join('|') | |
chgs = [] | |
@log.changes.each {|l| | |
chgs.concat(l.split("\n").delete_if {|ch| ch !~ /#{rx}/ }) | |
} | |
chgs.each {|change| | |
CL_STRINGS.each {|k,v| | |
if change =~ /#{v[:rx]}/ | |
@changes.add(k, change.clean_entry) | |
end | |
} | |
} | |
end | |
end | |
cl = ChangeLogger.new | |
$stdout.puts cl.to_s |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment