Last active
November 17, 2022 06:07
-
-
Save jesperronn/8418030 to your computer and use it in GitHub Desktop.
releasenotes script (with git)
This file contains hidden or 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 | |
# | |
# release notes script | |
# takes two git tags and prints any changes between them | |
# | |
# usage: | |
# ./releasenotes.sh [from] [to] [releasename] | |
# | |
# with [from] and [to] being git tags | |
#configuration section | |
#if not empty, will have a separate section for special words, listed if there is something to pay attention to | |
SPECIAL_WORDS = %w{sonar delete reverted cleanup fix accident problem} | |
RELATIVE_PATH_ROOT='..' | |
#ignored words when counting word occurrences | |
IGNORED_WORDS = %w(a are and as be by can for from have i id if in is it new no not of on so the that this to we with) | |
WORD_OCCURENCE_THRESHOLD=3 | |
WORD_OCCURENCE_MAX=50 | |
######################### | |
#find the following mentions of 'user story' | |
user_story_expr=/(RQ|U\d|US|QC|bug|XL|DE) ?\d\d\d\d?/i | |
#historically mentions of user stories: | |
#RQ1200 | |
#RQ 1200 | |
#RQ:1211 | |
#RQ-1539 | |
#US2704 | |
#U34623 | |
#QC 272 | |
#bug 274 | |
#XL-2862 | |
#DE160 | |
######################### | |
require 'date' | |
from_tag=ARGV[0] | |
to_tag=ARGV[1] | |
release_date=ARGV[2] | |
raise "FATAL! Missing arguments. Usage: ./releasenotes [from] [to] " unless [2,3].include? ARGV.size | |
raise "FATAL! [from] and [to] must not be the same!" if ARGV[0] == ARGV[1] | |
puts "RELEASE NOTES" | |
puts "=============" | |
puts "RELEASE Notes Here are some key figures derived from code changes in the given period." | |
puts | |
%x(mkdir -p .metadata) | |
FROMDATE = %x(git log -1 --pretty="%ad" --date=short #{from_tag}).strip | |
TODATE = %x(git log -1 --pretty="%ad" --date=short #{to_tag}).strip | |
TOTALCOMMITS = %x(git log --oneline --no-merges #{from_tag}..#{to_tag} | wc -l).strip | |
MOSTACTIVE = %x(git shortlog --summary --numbered --no-merges #{from_tag}..#{to_tag} > .metadata/AUTHORS.#{to_tag}.txt).strip | |
ALL_CHANGES = %x( git log --oneline --pretty="%s" #{from_tag}..#{to_tag} ) | |
SHORTSTAT = %x( git diff --shortstat -w #{from_tag}..#{to_tag} ) | |
dev_duration = Date.parse(TODATE) - Date.parse(FROMDATE) | |
release_delay = Date.parse(release_date) - Date.parse(TODATE) unless release_date.nil? | |
puts "From tag: #{from_tag}" | |
puts "To tag: #{to_tag}" | |
puts "Timespan: #{FROMDATE}..#{TODATE} (#{dev_duration.to_i} days)" | |
puts "Release date: #{release_date.nil? ? '?': release_date}" | |
puts " (released #{release_delay} days after last commit)" unless release_date.nil? | |
print "" | |
puts | |
puts "Number of commits: #{TOTALCOMMITS}" | |
puts " #{SHORTSTAT}" | |
puts;puts;puts | |
directories = %x(cat folderchanges.txt | grep -v "#" | grep -vE '^\s*$' ) | |
puts | |
puts "Number of changes in specific areas:" | |
puts "====================================" | |
result = [] | |
directories.lines.to_a.each do |dir| | |
out = %x(git log --oneline #{from_tag}..#{to_tag} -- "#{dir.strip}" | wc -l).strip | |
#puts dir | |
result << "#{dir.strip}: #{out}" | |
end | |
#puts result | |
inverted = [] | |
result.each do |line| | |
items = line.split(": ") | |
inverted << "#{items[1].rjust(6)} #{items[0]}" | |
end | |
STARTS_WITH_ZERO = /^\s*0\s/ | |
STRIP_COUNT=/^\s+\d+\s+/ | |
TRAILING_SLASH=/\/$/ | |
unknown_folders = inverted.select{|x| x.match(STARTS_WITH_ZERO)} | |
puts inverted.reject{|x| x.match(STARTS_WITH_ZERO)}.sort.reverse | |
puts | |
puts " Folders with no changes (or were not existing) for this period:" | |
puts unknown_folders.sort.map{|x| " " + x.gsub(STRIP_COUNT, '').sub(TRAILING_SLASH, '')} | |
puts;puts;puts | |
puts "User stories/defects mentioned in this release (#{TOTALCOMMITS} commits)" | |
puts "====================================" | |
matches = {}#hashmap with all RQ numbers as keys. Values [] of strings | |
ALL_CHANGES.split("\n").each do |line| | |
if line.match(user_story_expr) | |
rq_number = Regexp.last_match(0).upcase.gsub(/(:|-| )/, '') | |
matches[rq_number] = [] if matches[rq_number].nil? | |
matches[rq_number] << line | |
end | |
end | |
require 'yaml' | |
sorted_rqs = Hash[matches.sort] | |
puts sorted_rqs.map{|k,v| "#{k.rjust(8)} #{v.size.to_s.rjust(2)} changes"} | |
puts;puts;puts | |
puts "Most active committers:" | |
puts "====================================" | |
puts %x(cat .metadata/AUTHORS.#{to_tag}.txt) | |
puts;puts;puts | |
#awesome new way to get total number of changed lines | |
#first, generate list of authors which committed in this release: | |
# %x(git shortlog --summary --numbered --no-merges #{from_tag}..#{to_tag} | awk '{print $2}' > AUTHORS.txt) | |
#puts %x | |
#then sum up how many changes added from each commit (ignoring whitespace with '-w') | |
# lines from log is like this: | |
# 1 files changed, 8 insertions(+), 2 deletions(-) | |
# 1 files changed, 3 insertions(+), 6 deletions(-) | |
# 2 files changed, 33 insertions(+), 1 deletions(-) | |
#sum up these lines with awk '+=' | |
puts "Active committers -- changes in individual commits, ignoring whitespace" | |
puts "====================================" | |
puts %x(for NAME in `cat .metadata/AUTHORS.#{to_tag}.txt| awk '{print $2}'`; do git log --pretty="%h %an" -w --shortstat --author $NAME #{from_tag}..#{to_tag} | grep "(+)" | awk -v n=$NAME '{changed += $1} {added += $4} { removed += $6} END {print n, changed, "files changed" , added, "insertions(+)", removed, "deletions(-)" }' ; done) | |
puts;puts;puts | |
puts "#hashtags mentioned this release (#{TOTALCOMMITS} commits)" | |
puts "====================================" | |
matches = {} | |
ALL_CHANGES.split("\n").each do |line| | |
if line.match(/#\w+/) | |
hashtag = Regexp.last_match(0) | |
matches[hashtag] = [] if matches[hashtag].nil? | |
matches[hashtag] << line | |
end | |
end | |
sorted_hashtags = Hash[matches.sort].map{|k,v| "#{v.size.to_s.rjust(5)} #{k}"}.sort.reverse | |
#mentioned_once = sorted_hashtags.reject!{|x| x.match(/^\s*1\s/)} | |
puts sorted_hashtags | |
#puts " [#{mentioned_once.size} other hashtags mentioned only once]" | |
puts;puts;puts | |
puts "#{WORD_OCCURENCE_MAX} Most used words in this release (in commit comments)" | |
puts "====================================" | |
raw = %x(git log --oneline --pretty="%s" #{from_tag}..#{to_tag} ) | |
#to count occurrences | |
#example {"i"=>1, "was"=>2, "09809"=>1, "home"=>1, "yes"=>2, "you"=>1} | |
#from http://stackoverflow.com/questions/9675146/how-to-get-words-frequency-in-efficient-way-with-ruby | |
def count_words(words) | |
#words = string.split(' ') | |
frequency = Hash.new(0) | |
words.each { |word| frequency[word.downcase] += 1 } | |
frequency | |
end | |
arr = raw.scan(/\w+/) | |
arr2 = arr - IGNORED_WORDS | |
occurrences = count_words(arr2) | |
#occurrences = raw.scan(/\w+/).reduce(Hash.new{|i|0}){|res,w| break if IGNORED_WORDS.include? w; res[w.downcase]+=1;res} | |
#occurrences = raw.scan(/\w+/).each_with_object(Hash.new{|i|0}){|w,h| break if IGNORED_WORDS.include? w; h[w.downcase]+=1} | |
res = occurrences.sort_by{|k,v| -v}.select{|k,v| v >= WORD_OCCURENCE_THRESHOLD } | |
puts res[0..WORD_OCCURENCE_MAX].map{|a| "#{a[0]}:#{a[1]}" }.join(', ') | |
puts;puts;puts | |
puts "Full list of changes for this release" | |
puts "====================================" | |
%x{git log --oneline #{from_tag}..#{to_tag} --pretty="%s (%an) > .metadata/COMMITS.#{to_tag}.txt"} | |
puts "(will be in separate file)" | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment