Tested-by / Reported-by credits and more

Add tracking of tested-by, reported-by, and reviewed-by.  For the first
two, we also track who is *giving* those credits.

While I was in the neighborhood I also:

 - Started turning the "patch" class into something more than a bare
   container; this work has just begin.

 - Moved the report-writing code into its own file (reports.py)
This commit is contained in:
Jonathan Corbet 2008-11-11 11:11:04 -07:00
parent aacbb5f464
commit 9a2ba8a4f5
4 changed files with 410 additions and 194 deletions

View File

@ -21,6 +21,10 @@ class Hacker:
self.added = self.removed = 0
self.patches = [ ]
self.signoffs = [ ]
self.reviews = [ ]
self.tested = [ ]
self.reports = [ ]
self.testcred = self.repcred = 0
def addemail (self, email, elist):
self.email.append (email)
@ -41,8 +45,22 @@ class Hacker:
self.removed += patch.removed
self.patches.append (patch)
#
# There's got to be a better way.
#
def addsob (self, patch):
self.signoffs.append (patch)
def addreview (self, patch):
self.reviews.append (patch)
def addtested (self, patch):
self.tested.append (patch)
def addreport (self, patch):
self.reports.append (patch)
def reportcredit (self, patch):
self.repcred += 1
def testcredit (self, patch):
self.testcred += 1
HackersByName = { }
HackersByEmail = { }

265
gitdm
View File

@ -11,20 +11,15 @@
# Public License, version 2.
import database, csv, ConfigFile
import database, csv, ConfigFile, reports
import getopt, datetime
import os, re, sys, rfc822, string
from patterns import *
class patch:
pass
Today = datetime.date.today()
#
# Control options.
#
Outfile = sys.stdout
ListCount = 999999
MapUnknown = 0
DevReports = 1
DateStats = 0
@ -51,7 +46,7 @@ CFName = 'gitdm.config'
# -z Dump out the hacker database at completion
def ParseOpts ():
global Outfile, ListCount, MapUnknown, HTMLfile, DevReports
global MapUnknown, DevReports
global DateStats, AuthorSOBs, FileFilter, AkpmOverLt, DumpDB
global CFName, CSVFile
@ -66,11 +61,11 @@ def ParseOpts ():
elif opt[0] == '-D':
DateStats = 1
elif opt[0] == '-h':
HTMLfile = open (opt[1], 'w')
reports.SetHTMLOutput (open (opt[1], 'w'))
elif opt[0] == '-l':
ListCount = int (opt[1])
reports.SetMaxList (int (opt[1]))
elif opt[0] == '-o':
Outfile = open (opt[1], 'w')
reports.SetOutput (open (opt[1], 'w'))
elif opt[0] == '-r':
print 'Filter on "%s"' % (opt[1])
FileFilter = re.compile (opt[1])
@ -123,6 +118,29 @@ def PrintDateStats():
datef.write ('%d/%02d/%02d %6d %7d\n' % (date.year, date.month, date.day,
DateMap[date], total))
#
# Let's slowly try to move some smarts into this class.
#
class patch:
def __init__ (self, commit):
self.commit = commit
self.merge = self.added = self.removed = 0
self.author = LookupStoreHacker('Unknown hacker', 'unknown@hacker.net')
self.email = 'unknown@hacker.net'
self.sobs = [ ]
self.reviews = [ ]
self.testers = [ ]
self.reports = [ ]
def addreviewer (self, reviewer):
self.reviews.append (reviewer)
def addtester (self, tester):
self.testers.append (tester)
def addreporter (self, reporter):
self.reports.append (reporter)
#
# The core hack for grabbing the information about a changeset.
#
@ -137,12 +155,7 @@ def grabpatch():
if not NextLine:
return
p = patch()
p.commit = m.group (1)
p.merge = p.added = p.removed = 0
p.author = LookupStoreHacker('Unknown hacker', 'unknown@hacker.net')
p.email = 'unknown@hacker.net'
p.sobs = [ ]
p = patch(m.group (1))
NextLine = sys.stdin.readline ()
ignore = (FileFilter is not None)
while NextLine:
@ -173,6 +186,35 @@ def grabpatch():
p.sobs.append ((email, LookupStoreHacker(m.group (1), m.group (2))))
continue
#
# Various other tags of interest.
#
m = Preview.search (Line) # Reviewed-by:
if m:
email = database.RemapEmail (m.group (2))
p.addreviewer (LookupStoreHacker(m.group (1), email))
continue
m = Ptest.search (Line) # Tested-by:
if m:
email = database.RemapEmail (m.group (2))
p.addtester (LookupStoreHacker (m.group (1), email))
p.author.testcredit (patch)
continue
m = Prep.search (Line) # Reported-by:
if m:
email = database.RemapEmail (m.group (2))
p.addreporter (LookupStoreHacker (m.group (1), email))
p.author.reportcredit (patch)
continue
m = Preptest.search (Line) # Reported-and-tested-by:
if m:
email = database.RemapEmail (m.group (2))
h = LookupStoreHacker (m.group (1), email)
p.addreporter (h)
p.addtester (h)
p.author.reportcredit (patch)
p.author.testcredit (patch)
continue
#
# If this one is a merge, make note of the fact.
#
m = Pmerge.match (Line)
@ -253,170 +295,6 @@ def TrimLTSOBs (p):
if Linus in p.sobs and Akpm in p.sobs:
p.sobs.remove (Linus)
#
# HTML output support stuff.
#
HTMLfile = None
HTMLclass = 0
HClasses = ['Even', 'Odd']
THead = '''<p>
<table cellspacing=3>
<tr><th colspan=3>%s</th></tr>
'''
def BeginReport (title):
global HTMLclass
Outfile.write ('\n%s\n' % title)
if HTMLfile:
HTMLfile.write (THead % title)
HTMLclass = 0
TRow = ''' <tr class="%s">
<td>%s</td><td align="right">%d</td><td align="right">%.1f%%</td></tr>
'''
def ReportLine (text, count, pct):
global HTMLclass
if count == 0:
return
Outfile.write ('%-25s %4d (%.1f%%)\n' % (text, count, pct))
if HTMLfile:
HTMLfile.write (TRow % (HClasses[HTMLclass], text, count, pct))
HTMLclass ^= 1
def EndReport ():
if HTMLfile:
HTMLfile.write ('</table>\n\n')
#
# Comparison and report generation functions.
#
def ComparePCount (h1, h2):
return len (h2.patches) - len (h1.patches)
def ReportByPCount (hlist):
hlist.sort (ComparePCount)
count = 0
BeginReport ('Developers with the most changesets')
for h in hlist:
pcount = len (h.patches)
changed = max(h.added, h.removed)
delta = h.added - h.removed
if pcount > 0:
ReportLine (h.name, pcount, (pcount*100.0)/CSCount)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareLChanged (h1, h2):
return max(h2.added, h2.removed) - max(h1.added, h1.removed)
def ReportByLChanged (hlist):
hlist.sort (CompareLChanged)
count = 0
BeginReport ('Developers with the most changed lines')
for h in hlist:
pcount = len (h.patches)
changed = max(h.added, h.removed)
delta = h.added - h.removed
if (h.added + h.removed) > 0:
ReportLine (h.name, changed, (changed*100.0)/TotalChanged)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareLRemoved (h1, h2):
return (h2.removed - h2.added) - (h1.removed - h1.added)
def ReportByLRemoved (hlist):
hlist.sort (CompareLRemoved)
count = 0
BeginReport ('Developers with the most lines removed')
for h in hlist:
pcount = len (h.patches)
changed = max(h.added, h.removed)
delta = h.added - h.removed
if delta < 0:
ReportLine (h.name, -delta, (-delta*100.0)/TotalRemoved)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareEPCount (e1, e2):
return e2.count - e1.count
def ReportByPCEmpl (elist):
elist.sort (CompareEPCount)
count = 0
BeginReport ('Top changeset contributors by employer')
for e in elist:
if e.count != 0:
ReportLine (e.name, e.count, (e.count*100.0)/CSCount)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareELChanged (e1, e2):
return e2.changed - e1.changed
def ReportByELChanged (elist):
elist.sort (CompareELChanged)
count = 0
BeginReport ('Top lines changed by employer')
for e in elist:
if e.changed != 0:
ReportLine (e.name, e.changed, (e.changed*100.0)/TotalChanged)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareSOBs (h1, h2):
return len (h2.signoffs) - len (h1.signoffs)
def ReportBySOBs (hlist):
hlist.sort (CompareSOBs)
totalsobs = 0
for h in hlist:
totalsobs += len (h.signoffs)
count = 0
BeginReport ('Developers with the most signoffs (total %d)' % totalsobs)
for h in hlist:
scount = len (h.signoffs)
if scount > 0:
ReportLine (h.name, scount, (scount*100.0)/totalsobs)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareESOBs (e1, e2):
return e2.sobs - e1.sobs
def ReportByESOBs (elist):
elist.sort (CompareESOBs)
totalsobs = 0
for e in elist:
totalsobs += e.sobs
count = 0
BeginReport ('Employers with the most signoffs (total %d)' % totalsobs)
for e in elist:
if e.sobs > 0:
ReportLine (e.name, e.sobs, (e.sobs*100.0)/totalsobs)
count += 1
if count >= ListCount:
break
EndReport ()
#
# Here starts the real program.
@ -453,15 +331,21 @@ while (1):
p = grabpatch()
if not p:
break
if p.added > 100000 or p.removed > 100000:
print 'Skipping massive add'
continue
# if p.added > 100000 or p.removed > 100000:
# print 'Skipping massive add', p.commit
# continue
if FileFilter and p.added == 0 and p.removed == 0:
continue
if not p.merge:
p.author.addpatch (p)
for sobemail, sob in p.sobs:
sob.addsob (p)
for hacker in p.reviews:
hacker.addreview (p)
for hacker in p.testers:
hacker.addtested (p)
for hacker in p.reports:
hacker.addreport (p)
CSCount += 1
csv.AccumulatePatch (p)
print >> sys.stderr, 'Grabbing changesets...done'
@ -473,10 +357,10 @@ if DumpDB:
#
hlist = database.AllHackers ()
elist = database.AllEmployers ()
Outfile.write ('Processed %d csets from %d developers\n' % (CSCount,
reports.Write ('Processed %d csets from %d developers\n' % (CSCount,
len (hlist)))
Outfile.write ('%d employers found\n' % len (elist))
Outfile.write ('A total of %d lines added, %d removed (delta %d)\n' %
reports.Write ('%d employers found\n' % len (elist))
reports.Write ('A total of %d lines added, %d removed (delta %d)\n' %
(TotalAdded, TotalRemoved, TotalAdded - TotalRemoved))
if TotalChanged == 0:
TotalChanged = 1 # HACK to avoid div by zero
@ -489,10 +373,5 @@ if CSVFile is not None:
CSVFile.close ()
if DevReports:
ReportByPCount (hlist)
ReportByLChanged (hlist)
ReportByLRemoved (hlist)
ReportBySOBs (hlist)
ReportByPCEmpl (elist)
ReportByELChanged (elist)
ReportByESOBs (elist)
reports.DevReports (hlist, TotalChanged, CSCount, TotalRemoved)
reports.EmplReports (elist, TotalChanged, CSCount)

View File

@ -25,7 +25,10 @@ Prem = re.compile (r'^-[^-].*$')
Pdate = re.compile (r'^(Commit)?Date:\s+(.*)$')
Pfilea = re.compile (r'^---\s+(.*)$')
Pfileb = re.compile (r'^\+\+\+\s+(.*)$')
Preview = re.compile (r'Reviewed-by:\s+([^<]+)\s+<([^>]+)>')
Ptest = re.compile (r' tested-by:\s+([^<]+)\s+<([^>]+)>', re.I)
Prep = re.compile (r'Reported-by:\s+([^<]+)\s+<([^>]+)>')
Preptest = re.compile (r'reported-and-tested-by:\s+([^<]+)\s+<([^>]+)>', re.I)
#
# Merges are described with a variety of lines.
#

316
reports.py Normal file
View File

@ -0,0 +1,316 @@
#
# A new home for the reporting code.
#
import sys
Outfile = sys.stdout
HTMLfile = None
ListCount = 999999
def SetOutput (file):
global Outfile
Outfile = file
def SetHTMLOutput (file):
global HTMLfile
HTMLfile = file
def SetMaxList (max):
global ListCount
ListCount = max
def Write (stuff):
Outfile.write (stuff)
#
# HTML output support stuff.
#
HTMLclass = 0
HClasses = ['Even', 'Odd']
THead = '''<p>
<table cellspacing=3>
<tr><th colspan=3>%s</th></tr>
'''
def BeginReport (title):
global HTMLclass
Outfile.write ('\n%s\n' % title)
if HTMLfile:
HTMLfile.write (THead % title)
HTMLclass = 0
TRow = ''' <tr class="%s">
<td>%s</td><td align="right">%d</td><td align="right">%.1f%%</td></tr>
'''
def ReportLine (text, count, pct):
global HTMLclass
if count == 0:
return
Outfile.write ('%-25s %4d (%.1f%%)\n' % (text, count, pct))
if HTMLfile:
HTMLfile.write (TRow % (HClasses[HTMLclass], text, count, pct))
HTMLclass ^= 1
def EndReport ():
if HTMLfile:
HTMLfile.write ('</table>\n\n')
#
# Comparison and report generation functions.
#
def ComparePCount (h1, h2):
return len (h2.patches) - len (h1.patches)
def ReportByPCount (hlist, cscount):
hlist.sort (ComparePCount)
count = 0
BeginReport ('Developers with the most changesets')
for h in hlist:
pcount = len (h.patches)
changed = max(h.added, h.removed)
delta = h.added - h.removed
if pcount > 0:
ReportLine (h.name, pcount, (pcount*100.0)/cscount)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareLChanged (h1, h2):
return max(h2.added, h2.removed) - max(h1.added, h1.removed)
def ReportByLChanged (hlist, totalchanged):
hlist.sort (CompareLChanged)
count = 0
BeginReport ('Developers with the most changed lines')
for h in hlist:
pcount = len (h.patches)
changed = max(h.added, h.removed)
delta = h.added - h.removed
if (h.added + h.removed) > 0:
ReportLine (h.name, changed, (changed*100.0)/totalchanged)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareLRemoved (h1, h2):
return (h2.removed - h2.added) - (h1.removed - h1.added)
def ReportByLRemoved (hlist, totalremoved):
hlist.sort (CompareLRemoved)
count = 0
BeginReport ('Developers with the most lines removed')
for h in hlist:
pcount = len (h.patches)
changed = max(h.added, h.removed)
delta = h.added - h.removed
if delta < 0:
ReportLine (h.name, -delta, (-delta*100.0)/totalremoved)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareEPCount (e1, e2):
return e2.count - e1.count
def ReportByPCEmpl (elist, cscount):
elist.sort (CompareEPCount)
count = 0
BeginReport ('Top changeset contributors by employer')
for e in elist:
if e.count != 0:
ReportLine (e.name, e.count, (e.count*100.0)/cscount)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareELChanged (e1, e2):
return e2.changed - e1.changed
def ReportByELChanged (elist, totalchanged):
elist.sort (CompareELChanged)
count = 0
BeginReport ('Top lines changed by employer')
for e in elist:
if e.changed != 0:
ReportLine (e.name, e.changed, (e.changed*100.0)/totalchanged)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareSOBs (h1, h2):
return len (h2.signoffs) - len (h1.signoffs)
def ReportBySOBs (hlist):
hlist.sort (CompareSOBs)
totalsobs = 0
for h in hlist:
totalsobs += len (h.signoffs)
count = 0
BeginReport ('Developers with the most signoffs (total %d)' % totalsobs)
for h in hlist:
scount = len (h.signoffs)
if scount > 0:
ReportLine (h.name, scount, (scount*100.0)/totalsobs)
count += 1
if count >= ListCount:
break
EndReport ()
#
# Reviewer reporting.
#
def CompareRevs (h1, h2):
return len (h2.reviews) - len (h1.reviews)
def ReportByRevs (hlist):
hlist.sort (CompareRevs)
totalrevs = 0
for h in hlist:
totalrevs += len (h.reviews)
count = 0
BeginReport ('Developers with the most reviews (total %d)' % totalrevs)
for h in hlist:
scount = len (h.reviews)
if scount > 0:
ReportLine (h.name, scount, (scount*100.0)/totalrevs)
count += 1
if count >= ListCount:
break
EndReport ()
#
# tester reporting.
#
def CompareTests (h1, h2):
return len (h2.tested) - len (h1.tested)
def ReportByTests (hlist):
hlist.sort (CompareTests)
totaltests = 0
for h in hlist:
totaltests += len (h.tested)
count = 0
BeginReport ('Developers with the most test credits (total %d)' % totaltests)
for h in hlist:
scount = len (h.tested)
if scount > 0:
ReportLine (h.name, scount, (scount*100.0)/totaltests)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareTestCred (h1, h2):
return h2.testcred - h1.testcred
def ReportByTestCreds (hlist):
hlist.sort (CompareTestCred)
totaltests = 0
for h in hlist:
totaltests += h.testcred
count = 0
BeginReport ('Developers who gave the most tested-by credits (total %d)' % totaltests)
for h in hlist:
if h.testcred > 0:
ReportLine (h.name, h.testcred, (h.testcred*100.0)/totaltests)
count += 1
if count >= ListCount:
break
EndReport ()
#
# Reporter reporting.
#
def CompareReports (h1, h2):
return len (h2.reports) - len (h1.reports)
def ReportByReports (hlist):
hlist.sort (CompareReports)
totalreps = 0
for h in hlist:
totalreps += len (h.reports)
count = 0
BeginReport ('Developers with the most report credits (total %d)' % totalreps)
for h in hlist:
scount = len (h.reports)
if scount > 0:
ReportLine (h.name, scount, (scount*100.0)/totalreps)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareRepCred (h1, h2):
return h2.repcred - h1.repcred
def ReportByRepCreds (hlist):
hlist.sort (CompareRepCred)
totalreps = 0
for h in hlist:
totalreps += h.repcred
count = 0
BeginReport ('Developers who gave the most report credits (total %d)' % totalreps)
for h in hlist:
if h.repcred > 0:
ReportLine (h.name, h.repcred, (h.repcred*100.0)/totalreps)
count += 1
if count >= ListCount:
break
EndReport ()
def CompareESOBs (e1, e2):
return e2.sobs - e1.sobs
def ReportByESOBs (elist):
elist.sort (CompareESOBs)
totalsobs = 0
for e in elist:
totalsobs += e.sobs
count = 0
BeginReport ('Employers with the most signoffs (total %d)' % totalsobs)
for e in elist:
if e.sobs > 0:
ReportLine (e.name, e.sobs, (e.sobs*100.0)/totalsobs)
count += 1
if count >= ListCount:
break
EndReport ()
def DevReports (hlist, totalchanged, cscount, totalremoved):
ReportByPCount (hlist, cscount)
ReportByLChanged (hlist, totalchanged)
ReportByLRemoved (hlist, totalremoved)
ReportBySOBs (hlist)
ReportByRevs (hlist)
ReportByTests (hlist)
ReportByTestCreds (hlist)
ReportByReports (hlist)
ReportByRepCreds (hlist)
def EmplReports (elist, totalchanged, cscount):
ReportByPCEmpl (elist, cscount)
ReportByELChanged (elist, totalchanged)
ReportByESOBs (elist)