Added a responsibilities parameter that shows which author has done what.

The responsibilities module shows a maximum of 10 files, per author, that each
author is responsible for. This fucntionality can be enabled by supplying -r
or --responsibilities to gitinspector. The parameter is also enabled if
--tda367 is specified.
This commit is contained in:
Adam Waldenberg 2012-05-25 14:27:28 +02:00
parent 0fa883eb55
commit 949b62736c
5 changed files with 101 additions and 15 deletions

View File

@ -38,13 +38,14 @@ __thread_lock__ = threading.BoundedSemaphore(NUM_THREADS)
__blame_lock__ = threading.Lock()
class BlameThread(threading.Thread):
def __init__(self, blame_string, extension, blames):
def __init__(self, blame_string, extension, blames, filename):
__thread_lock__.acquire() # Lock controlling the number of threads running
threading.Thread.__init__(self)
self.blame_string = blame_string
self.extension = extension
self.blames = blames
self.filename = filename
def run(self):
git_blame_r = os.popen(self.blame_string)
@ -56,20 +57,20 @@ class BlameThread(threading.Thread):
content = Blame.get_content(j)
__blame_lock__.acquire() # Global lock used to protect calls from here...
if self.blames.get(author, None) == None:
self.blames[author] = BlameEntry()
if self.blames.get((author, self.filename), None) == None:
self.blames[(author, self.filename)] = BlameEntry()
if comment.is_comment(self.extension, content):
self.blames[author].comments += 1
self.blames[(author, self.filename)].comments += 1
if is_inside_comment:
if comment.has_comment_end(self.extension, content):
is_inside_comment = False
else:
self.blames[author].comments += 1
self.blames[(author, self.filename)].comments += 1
elif comment.has_comment_begining(self.extension, content):
is_inside_comment = True
self.blames[author].rows += 1
self.blames[(author, self.filename)].rows += 1
__blame_lock__.release() # ...to here.
__thread_lock__.release() # Lock controlling the number of threads running
@ -84,7 +85,7 @@ class Blame:
if FileDiff.is_valid_extension(row) and not filtering.set_filtered(FileDiff.get_filename(row)):
if not missing.add(row.strip()):
blame_string = "git blame -w {0} \"".format("-C -C -M" if hard else "") + row.strip() + "\""
thread = BlameThread(blame_string, FileDiff.get_extension(row), self.blames)
thread = BlameThread(blame_string, FileDiff.get_extension(row), self.blames, row.strip())
thread.daemon = True
thread.start()
@ -116,17 +117,37 @@ class Blame:
content = re.search(" \d+\)(.*)", string)
return content.group(1).lstrip()
def get_summed_blames(self):
summed_blames = {}
for i in self.blames.items():
if summed_blames.get(i[0][0], None) == None:
summed_blames[i[0][0]] = BlameEntry()
summed_blames[i[0][0]].rows += i[1].rows
summed_blames[i[0][0]].comments += i[1].comments
return summed_blames
__blame__ = None
def get(hard):
global __blame__
if __blame__ == None:
__blame__ = Blame(hard)
return __blame__
def output(hard):
print ""
blame = Blame(hard)
get(hard)
if hard and sys.stdout.isatty():
terminal.clear_row()
print "\bBelow is the number of rows from each author that have survived and"
print "{0}Below is the number of rows from each author that have survived and".format("\b" if sys.stdout.isatty() else "")
print "are still intact in the current revision:\n"
terminal.printb("Author".ljust(21) + "Rows".rjust(10) + "% in comments".rjust(16))
for i in sorted(blame.blames.items()):
for i in sorted(__blame__.get_summed_blames().items()):
print i[0].ljust(20)[0:20],
print str(i[1].rows).rjust(10),
print "{0:.2f}".format(100.0 * i[1].comments / i[1].rows).rjust(15)

View File

@ -146,7 +146,6 @@ def get(hard):
return __changes__
def output(hard):
get(hard)
authorinfo_list = get(hard).get_authorinfo_list()
total_changes = 0.0

View File

@ -27,6 +27,7 @@ import help
import metrics
import missing
import os
import responsibilities
import sys
import terminal
import timeline
@ -38,6 +39,7 @@ class Runner:
self.include_metrics = False
self.list_file_types = False
self.repo = "."
self.responsibilities = False
self.tda367 = False
self.timeline = False
self.useweeks = False
@ -57,6 +59,9 @@ class Runner:
if self.include_metrics:
metrics.output()
if self.responsibilities:
responsibilities.output(self.hard)
missing.output()
filtering.output()
@ -69,9 +74,9 @@ if __name__ == "__main__":
__run__ = Runner()
try:
__opts__, __args__ = getopt.gnu_getopt(sys.argv[1:], "cf:hHlmTwx:", ["checkout-missing", "exclude=", "file-types=",
"hard", "help", "list-file-types", "metrics","tda367", "timeline",
"version"])
__opts__, __args__ = getopt.gnu_getopt(sys.argv[1:], "cf:hHlmrTwx:", ["checkout-missing", "exclude=",
"file-types=", "hard", "help", "list-file-types", "metrics",
"responsibilities", "tda367", "timeline", "version"])
except getopt.error, msg:
print sys.argv[0], "\b:", msg
print "Try `", sys.argv[0], "--help' for more information."
@ -90,12 +95,15 @@ if __name__ == "__main__":
__run__.list_file_types = True
elif o in("-m", "--metrics"):
__run__.include_metrics = True
elif o in("-r", "--responsibilities"):
__run__.responsibilities = True
elif o in("--version"):
version.output()
sys.exit(0)
elif o in("--tda367"):
__run__.include_metrics = True
__run__.list_file_types = True
__run__.responsibilities = True
__run__.tda367 = True
__run__.timeline = True
__run__.useweeks = True

View File

@ -34,10 +34,12 @@ Mandatory arguments to long options are mandatory for short options too.
current branch of the repository
-m --metrics include checks for certain metrics during the
analysis of commits
-r --responsibilities show which files the different authors seem
most responsible for
-T, --timeline show commit timeline, including author names
--tda367 show statistics and information in a way that
is formatted for the course TDA367/DIT211;
this is the same as supplying -lmTw
this is the same as supplying -lmrTw
-w show all statistical information in weeks
instead of in months
-x, --exclude=PATTERN an exclusion pattern describing file names that

56
responsibilities.py Normal file
View File

@ -0,0 +1,56 @@
# coding: utf-8
#
# Copyright © 2012 Ejwa Software. All rights reserved.
#
# This file is part of gitinspector.
#
# gitinspector is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gitinspector is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
import blame
import terminal
class ResponsibiltyEntry:
blames = {}
class Responsibilities:
@staticmethod
def get(hard, author_name):
author_blames = {}
for i in blame.get(hard).blames.items():
if (author_name == i[0][0]):
total_rows = i[1].rows - i[1].comments
if total_rows > 0:
author_blames[i[0][1]] = total_rows
return sorted(author_blames.items())
def output(hard):
print "\nThe following repsonsibilties, by author, were found in the current"
print "revision of the repository (comments are exluded from the line count,"
print "if possible):"
for i in sorted(set(i[0] for i in blame.get(hard).blames)):
print "\n" + i, "is mostly responsible for:"
responsibilities = sorted(((i[1], i[0]) for i in Responsibilities.get(hard, i)), reverse=True)
for j, entry in enumerate(responsibilities):
(width, _) = terminal.get_size()
width -= 7
print str(entry[0]).rjust(6),
print "...%s" % entry[1][-width+3:] if len(entry[1]) > width else entry[1]
if j >= 9:
break