diff options
author | Jonathan Corbet <corbet@lwn.net> | 2013-07-23 14:02:26 -0600 |
---|---|---|
committer | Jonathan Corbet <corbet@lwn.net> | 2013-07-23 14:02:26 -0600 |
commit | 4a30d22c6bf780b714b6fdfe46249c9ac1e8c049 (patch) | |
tree | a075b87eaaf954403caa5a920899951037a8a585 | |
parent | 500b460c4683faeeefa6bf50afcc6a4f018b3b5d (diff) |
A simple tool for generating changelog stats.
-rwxr-xr-x | changelogs | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/changelogs b/changelogs new file mode 100755 index 0000000..b0e2232 --- /dev/null +++ b/changelogs @@ -0,0 +1,273 @@ +#!/usr/bin/pypy +# -*- python -*- +# +# Go munging through changelogs for interesting info. +# +# git log <whatever> | changelogs +# +from patterns import patterns +import re, sys + + +SavedLine = '' + +def getline(input): + global SavedLine + if SavedLine: + ret = SavedLine + SavedLine = '' + return ret + l = input.readline() + if l: + return l.rstrip() + return None + +def SaveLine(line): + global SavedLine + SavedLine = line + +# +# A simple state machine based on where we are in the patch. The +# first stuff we get is the header. +# +S_HEADER = 0 +# +# Then comes the single-line description. +# +S_DESC = 1 +# +# ...the full changelog... +# +S_CHANGELOG = 2 +# +# ...and the tag section. +# +S_TAGS = 3 +S_DONE = 4 + +# +# The functions to handle each of these states. +# +def get_header(patch, line, input): + if line == '': + return S_DESC + m = patterns['author'].match(line) + if m: + patch.author = m.group(1) + return S_HEADER + +def get_desc(patch, line, input): + if not line: + print 'Missing desc in', patch.commit + return S_CHANGELOG + patch.desc = line + #print 'desc', patch.desc + line = getline(input) + if line: + print 'Weird post-desc line in', patch.commit + return S_CHANGELOG + +tagline = re.compile(r'^\s+(([-a-z]+-by)|cc):.*@.*$', re.I) +def get_changelog(patch, line, input): + #print 'cl', line + if not line: + if patch.templog: + patch.changelog += patch.templog + patch.templog = '' + if patterns['commit'].match(line): + # No changelog at all - usually a Linus tag + SaveLine(line) + return S_DONE + elif tagline.match(line): + if patch.templog: + patch.changelog += patch.templog + return get_tag(patch, line, input) + else: + patch.templog += line + '\n' + return S_CHANGELOG + +def get_tag(patch, line, input): + # + # Some people put blank lines in the middle of tags. + # + #print 'tag', line + if not line: + return S_TAGS + # + # A new commit line says we've gone too far. + # + if patterns['commit'].match(line): + SaveLine(line) + return S_DONE + m = patterns['signed-off-by'].match(line) + if m: + patch.signoffs.append(m.group(2)) + else: + # + # Look for other tags indicating that somebody at least + # looked at the patch. + # + for tag in ('acked-by', 'reviewed-by', 'tested-by'): + if patterns[tag].match(line): + patch.othertags += 1 + break + return S_TAGS + +grabbers = [ get_header, get_desc, get_changelog, get_tag ] + + +# +# A variant on the gitdm patch class. +# +class patch: + def __init__(self, commit): + self.commit = commit + self.desc = '' + self.changelog = '' + self.templog = '' + self.author = '' + self.signoffs = [ ] + self.othertags = 0 + +def grabpatch(input): + # + # If it's not a patch something is screwy. + # + line = getline(input) + if line is None: + return None + m = patterns['commit'].match(line) + if not m: + print 'noncommit', line + return None + p = patch(m.group(1)) + state = S_HEADER + # + # Crank through the patch. + # + while state != S_DONE: + line = getline(input) + if line is None: + if state != S_TAGS: + print 'Ran out of patch' + return p + state = grabbers[state](p, line, input) + return p + +# +# Stats gathering. +# +EmptyCulprits = { } +SingleSSCulprits = { } +NoSOBCulprits = { } + +def LogEmptyCulprit(culprit): + try: + EmptyCulprits[culprit] += 1 + except KeyError: + EmptyCulprits[culprit] = 1 + +def LogSSCulprit(culprit): + try: + SingleSSCulprits[culprit] += 1 + except KeyError: + SingleSSCulprits[culprit] = 1 + +def LogNoSOBCulprit(culprit): + try: + NoSOBCulprits[culprit] += 1 + except KeyError: + NoSOBCulprits[culprit] = 1 + +def SortedCulprits(culprits): + def compare(c1, c2): + return culprits[c2] - culprits[c1] + names = culprits.keys() + names.sort(compare) + return names + +def PrintCulprits(culprits, sorted): + for name in sorted: + print '\t%30s: %d' % (name, culprits[name]) +# +# Patch logging +# +SSPatches = { } +EmptyCLPatches = { } +NoSOBPatches = { } + +def LogSSPatch(p): + LogSSCulprit(p.author) + try: + SSPatches[p.author].append(p) + except KeyError: + SSPatches[p.author] = [p] + +def LogECLPatch(p): + LogEmptyCulprit(p.author) + try: + EmptyCLPatches[p.author].append(p) + except KeyError: + EmptyCLPatches[p.author] = [p] + +def LogNoSOB(p): + LogNoSOBCulprit(p.author) + try: + NoSOBPatches[p.author].append(p) + except KeyError: + NoSOBPatches[p.author] = [p] + +LinusURL = 'http://git.kernel.org/linus/' +def WritePatches(names, patches, file): + out = open(file, 'w') + for name in names: + out.write('<h4>%s</h4>\n<ul>\n' % name) + for p in patches[name]: + out.write('\t<li><a href="%s%s"><tt>%s</tt></a> %s\n' % (LinusURL, + p.commit, + p.commit, + p.desc)) + out.write('</ul>\n\n') + out.close() + +# +# Main program. +# +Npatches = 0 +NemptyCL = 0 + +Nsinglesob = 0 +Nnosob = 0 + +p = grabpatch(sys.stdin) +while p: + #print p.commit, len(p.changelog) + Npatches += 1 + if len(p.changelog) == 0: + NemptyCL += 1 + LogECLPatch(p) + if len(p.signoffs) == 0: + Nnosob += 1 + LogNoSOB(p) + elif len(p.signoffs) == 1 and p.othertags == 0: + Nsinglesob += 1 + LogSSPatch(p) + p = grabpatch(sys.stdin) + +print '%d patches, %d w/o changelog' % (Npatches, NemptyCL) +print ' %d w/o signoff, %d w/1 signoff, %d SS culprits' % (Nnosob, Nsinglesob, + len(SingleSSCulprits)) +print '\nMost single signoffs:' +sorted = SortedCulprits(SingleSSCulprits)[:20] +PrintCulprits(SingleSSCulprits, sorted) +WritePatches(sorted, SSPatches, 'sspatches.html') + +print '\nMost empty changelogs:' +sorted = SortedCulprits(EmptyCulprits)[:20] +PrintCulprits(EmptyCulprits, sorted) +WritePatches(sorted, EmptyCLPatches, 'emptypatches.html') + +print '\nNoSOB:' +sorted = SortedCulprits(NoSOBCulprits) +PrintCulprits(NoSOBCulprits, sorted) +WritePatches(sorted, NoSOBPatches, 'nosobpatches.html') |