Support for multiple repositories is near completion (See issue #24).

Only the metrics module is lacking support. While the rest should work,
please note that it is completely untested as of now and probably
has some rough edges.
This commit is contained in:
Adam Waldenberg 2015-10-31 05:10:00 +01:00
parent 46b21db196
commit 98615ccbfc
5 changed files with 45 additions and 41 deletions

View File

@ -148,6 +148,17 @@ class Blame(object):
for i in range(0, NUM_THREADS): for i in range(0, NUM_THREADS):
__thread_lock__.acquire() __thread_lock__.acquire()
# We also have to release them for future use.
for i in range(0, NUM_THREADS):
__thread_lock__.release()
def __add__(self, other):
if other == None:
return self
self.blames.update(other.blames)
return self
@staticmethod @staticmethod
def is_revision(string): def is_revision(string):
revision = re.search("([0-9a-f]{40})", string) revision = re.search("([0-9a-f]{40})", string)
@ -180,12 +191,3 @@ class Blame(object):
summed_blames[i[0][0]].comments += i[1].comments summed_blames[i[0][0]].comments += i[1].comments
return summed_blames return summed_blames
__blame__ = None
def get(hard, useweeks, changes):
global __blame__
if __blame__ == None:
__blame__ = Blame(hard, useweeks, changes)
return __blame__

View File

@ -23,6 +23,7 @@ import atexit
import getopt import getopt
import os import os
import sys import sys
from .blame import Blame
from .changes import Changes from .changes import Changes
from .config import GitConfig from .config import GitConfig
from . import (basedir, clone, extensions, filtering, format, help, interval, from . import (basedir, clone, extensions, filtering, format, help, interval,
@ -58,13 +59,16 @@ class Runner(object):
terminal.skip_escapes(not sys.stdout.isatty()) terminal.skip_escapes(not sys.stdout.isatty())
terminal.set_stdout_encoding() terminal.set_stdout_encoding()
previous_directory = os.getcwd() previous_directory = os.getcwd()
changes = None summed_blames = None
summed_changes = None
for repo in repos: for repo in repos:
os.chdir(previous_directory) os.chdir(previous_directory)
os.chdir(repo) os.chdir(repo)
absolute_path = basedir.get_basedir_git() absolute_path = basedir.get_basedir_git()
changes = Changes(self.hard) + changes changes = Changes(self.hard)
summed_blames = Blame(self.hard, self.useweeks, changes) + summed_blames
summed_changes = changes + summed_changes
if sys.stdout.isatty() and format.is_interactive_format(): if sys.stdout.isatty() and format.is_interactive_format():
terminal.clear_row() terminal.clear_row()
@ -75,16 +79,16 @@ class Runner(object):
outputable.output(ChangesOutput(changes)) outputable.output(ChangesOutput(changes))
if changes.get_commits(): if changes.get_commits():
outputable.output(BlameOutput(changes, self.hard, self.useweeks)) outputable.output(BlameOutput(summed_changes, summed_blames))
if self.timeline: if self.timeline:
outputable.output(TimelineOutput(changes, self.useweeks)) outputable.output(TimelineOutput(summed_changes, self.useweeks))
if self.include_metrics: if self.include_metrics:
outputable.output(MetricsOutput()) outputable.output(MetricsOutput())
if self.responsibilities: if self.responsibilities:
outputable.output(ResponsibilitiesOutput(changes, self.hard, self.useweeks)) outputable.output(ResponsibilitiesOutput(summed_changes, summed_blames))
outputable.output(FilteringOutput()) outputable.output(FilteringOutput())

View File

@ -23,21 +23,20 @@ import json
import sys import sys
import textwrap import textwrap
from ..localization import N_ from ..localization import N_
from .. import blame, format, gravatar, terminal from .. import format, gravatar, terminal
from ..blame import Blame
from .outputable import Outputable from .outputable import Outputable
BLAME_INFO_TEXT = N_("Below are the number of rows from each author that have survived and are still " BLAME_INFO_TEXT = N_("Below are the number of rows from each author that have survived and are still "
"intact in the current revision") "intact in the current revision")
class BlameOutput(Outputable): class BlameOutput(Outputable):
def __init__(self, changes, hard, useweeks): def __init__(self, changes, blame):
if format.is_interactive_format(): if format.is_interactive_format():
print("") print("")
self.changes = changes self.changes = changes
self.hard = hard self.blame = blame
self.useweeks = useweeks
blame.get(self.hard, self.useweeks, self.changes)
Outputable.__init__(self) Outputable.__init__(self)
def output_html(self): def output_html(self):
@ -47,7 +46,7 @@ class BlameOutput(Outputable):
_("Author"), _("Rows"), _("Stability"), _("Age"), _("% in comments")) _("Author"), _("Rows"), _("Stability"), _("Age"), _("% in comments"))
blame_xml += "<tbody>" blame_xml += "<tbody>"
chart_data = "" chart_data = ""
blames = sorted(blame.__blame__.get_summed_blames().items()) blames = sorted(self.blame.get_summed_blames().items())
total_blames = 0 total_blames = 0
for i in blames: for i in blames:
@ -64,7 +63,7 @@ class BlameOutput(Outputable):
blame_xml += "<td>" + entry[0] + "</td>" blame_xml += "<td>" + entry[0] + "</td>"
blame_xml += "<td>" + str(entry[1].rows) + "</td>" blame_xml += "<td>" + str(entry[1].rows) + "</td>"
blame_xml += "<td>" + ("{0:.1f}".format(blame.Blame.get_stability(entry[0], entry[1].rows, self.changes)) + "</td>") blame_xml += "<td>" + ("{0:.1f}".format(Blame.get_stability(entry[0], entry[1].rows, self.changes)) + "</td>")
blame_xml += "<td>" + "{0:.1f}".format(float(entry[1].skew) / entry[1].rows) + "</td>" blame_xml += "<td>" + "{0:.1f}".format(float(entry[1].skew) / entry[1].rows) + "</td>"
blame_xml += "<td>" + "{0:.2f}".format(100.0 * entry[1].comments / entry[1].rows) + "</td>" blame_xml += "<td>" + "{0:.2f}".format(100.0 * entry[1].comments / entry[1].rows) + "</td>"
blame_xml += "<td style=\"display: none\">" + work_percentage + "</td>" blame_xml += "<td style=\"display: none\">" + work_percentage + "</td>"
@ -99,13 +98,13 @@ class BlameOutput(Outputable):
message_xml = "\t\t\t\"message\": \"" + _(BLAME_INFO_TEXT) + "\",\n" message_xml = "\t\t\t\"message\": \"" + _(BLAME_INFO_TEXT) + "\",\n"
blame_xml = "" blame_xml = ""
for i in sorted(blame.__blame__.get_summed_blames().items()): for i in sorted(self.blame.get_summed_blames().items()):
author_email = self.changes.get_latest_email_by_author(i[0]) author_email = self.changes.get_latest_email_by_author(i[0])
name_xml = "\t\t\t\t\"name\": \"" + i[0] + "\",\n" name_xml = "\t\t\t\t\"name\": \"" + i[0] + "\",\n"
gravatar_xml = "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(author_email) + "\",\n" gravatar_xml = "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(author_email) + "\",\n"
rows_xml = "\t\t\t\t\"rows\": " + str(i[1].rows) + ",\n" rows_xml = "\t\t\t\t\"rows\": " + str(i[1].rows) + ",\n"
stability_xml = ("\t\t\t\t\"stability\": " + "{0:.1f}".format(blame.Blame.get_stability(i[0], i[1].rows, stability_xml = ("\t\t\t\t\"stability\": " + "{0:.1f}".format(Blame.get_stability(i[0], i[1].rows,
self.changes)) + ",\n") self.changes)) + ",\n")
age_xml = ("\t\t\t\t\"age\": " + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + ",\n") age_xml = ("\t\t\t\t\"age\": " + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + ",\n")
percentage_in_comments_xml = ("\t\t\t\t\"percentage-in-comments\": " + "{0:.2f}".format(100.0 * i[1].comments / i[1].rows) + percentage_in_comments_xml = ("\t\t\t\t\"percentage-in-comments\": " + "{0:.2f}".format(100.0 * i[1].comments / i[1].rows) +
@ -125,10 +124,10 @@ class BlameOutput(Outputable):
terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Rows"), 10) + terminal.rjust(_("Stability"), 15) + terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Rows"), 10) + terminal.rjust(_("Stability"), 15) +
terminal.rjust(_("Age"), 13) + terminal.rjust(_("% in comments"), 20)) terminal.rjust(_("Age"), 13) + terminal.rjust(_("% in comments"), 20))
for i in sorted(blame.__blame__.get_summed_blames().items()): for i in sorted(self.blame.get_summed_blames().items()):
print(terminal.ljust(i[0], 20)[0:20 - terminal.get_excess_column_count(i[0])], end=" ") print(terminal.ljust(i[0], 20)[0:20 - terminal.get_excess_column_count(i[0])], end=" ")
print(str(i[1].rows).rjust(10), end=" ") print(str(i[1].rows).rjust(10), end=" ")
print("{0:.1f}".format(blame.Blame.get_stability(i[0], i[1].rows, self.changes)).rjust(14), end=" ") print("{0:.1f}".format(Blame.get_stability(i[0], i[1].rows, self.changes)).rjust(14), end=" ")
print("{0:.1f}".format(float(i[1].skew) / i[1].rows).rjust(12), end=" ") print("{0:.1f}".format(float(i[1].skew) / i[1].rows).rjust(12), end=" ")
print("{0:.2f}".format(100.0 * i[1].comments / i[1].rows).rjust(19)) print("{0:.2f}".format(100.0 * i[1].comments / i[1].rows).rjust(19))
@ -136,13 +135,13 @@ class BlameOutput(Outputable):
message_xml = "\t\t<message>" + _(BLAME_INFO_TEXT) + "</message>\n" message_xml = "\t\t<message>" + _(BLAME_INFO_TEXT) + "</message>\n"
blame_xml = "" blame_xml = ""
for i in sorted(blame.__blame__.get_summed_blames().items()): for i in sorted(self.blame.get_summed_blames().items()):
author_email = self.changes.get_latest_email_by_author(i[0]) author_email = self.changes.get_latest_email_by_author(i[0])
name_xml = "\t\t\t\t<name>" + i[0] + "</name>\n" name_xml = "\t\t\t\t<name>" + i[0] + "</name>\n"
gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(author_email) + "</gravatar>\n" gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(author_email) + "</gravatar>\n"
rows_xml = "\t\t\t\t<rows>" + str(i[1].rows) + "</rows>\n" rows_xml = "\t\t\t\t<rows>" + str(i[1].rows) + "</rows>\n"
stability_xml = ("\t\t\t\t<stability>" + "{0:.1f}".format(blame.Blame.get_stability(i[0], i[1].rows, stability_xml = ("\t\t\t\t<stability>" + "{0:.1f}".format(Blame.get_stability(i[0], i[1].rows,
self.changes)) + "</stability>\n") self.changes)) + "</stability>\n")
age_xml = ("\t\t\t\t<age>" + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + "</age>\n") age_xml = ("\t\t\t\t<age>" + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + "</age>\n")
percentage_in_comments_xml = ("\t\t\t\t<percentage-in-comments>" + "{0:.2f}".format(100.0 * i[1].comments / i[1].rows) + percentage_in_comments_xml = ("\t\t\t\t<percentage-in-comments>" + "{0:.2f}".format(100.0 * i[1].comments / i[1].rows) +

View File

@ -21,7 +21,7 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import textwrap import textwrap
from ..localization import N_ from ..localization import N_
from .. import blame, format, gravatar, terminal from .. import format, gravatar, terminal
from .. import responsibilities as resp from .. import responsibilities as resp
from .outputable import Outputable from .outputable import Outputable
@ -31,18 +31,17 @@ RESPONSIBILITIES_INFO_TEXT = N_("The following responsibilities, by author, were
MOSTLY_RESPONSIBLE_FOR_TEXT = N_("is mostly responsible for") MOSTLY_RESPONSIBLE_FOR_TEXT = N_("is mostly responsible for")
class ResponsibilitiesOutput(Outputable): class ResponsibilitiesOutput(Outputable):
def __init__(self, changes, hard, useweeks): def __init__(self, changes, blame):
self.changes = changes self.changes = changes
self.hard = hard self.blame = blame
self.useweeks = useweeks
Outputable.__init__(self) Outputable.__init__(self)
def output_text(self): def output_text(self):
print("\n" + textwrap.fill(_(RESPONSIBILITIES_INFO_TEXT) + ":", width=terminal.get_size()[0])) print("\n" + textwrap.fill(_(RESPONSIBILITIES_INFO_TEXT) + ":", width=terminal.get_size()[0]))
for i in sorted(set(i[0] for i in blame.get(self.hard, self.useweeks, self.changes).blames)): for i in sorted(set(i[0] for i in self.blame.blames)):
responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.changes, responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.changes,
self.hard, self.useweeks, i)), reverse=True) self.blame, i)), reverse=True)
if responsibilities: if responsibilities:
print("\n" + i, _(MOSTLY_RESPONSIBLE_FOR_TEXT) + ":") print("\n" + i, _(MOSTLY_RESPONSIBLE_FOR_TEXT) + ":")
@ -60,9 +59,9 @@ class ResponsibilitiesOutput(Outputable):
resp_xml = "<div><div class=\"box\" id=\"responsibilities\">" resp_xml = "<div><div class=\"box\" id=\"responsibilities\">"
resp_xml += "<p>" + _(RESPONSIBILITIES_INFO_TEXT) + ".</p>" resp_xml += "<p>" + _(RESPONSIBILITIES_INFO_TEXT) + ".</p>"
for i in sorted(set(i[0] for i in blame.get(self.hard, self.useweeks, self.changes).blames)): for i in sorted(set(i[0] for i in self.blame.blames)):
responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.changes, responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.changes,
self.hard, self.useweeks, i)), reverse=True) self.blame, i)), reverse=True)
if responsibilities: if responsibilities:
resp_xml += "<div>" resp_xml += "<div>"
@ -87,9 +86,9 @@ class ResponsibilitiesOutput(Outputable):
message_xml = "\t\t\t\"message\": \"" + _(RESPONSIBILITIES_INFO_TEXT) + "\",\n" message_xml = "\t\t\t\"message\": \"" + _(RESPONSIBILITIES_INFO_TEXT) + "\",\n"
resp_xml = "" resp_xml = ""
for i in sorted(set(i[0] for i in blame.get(self.hard, self.useweeks, self.changes).blames)): for i in sorted(set(i[0] for i in self.blame.blames)):
responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.changes, responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.changes,
self.hard, self.useweeks, i)), reverse=True) self.blame, i)), reverse=True)
if responsibilities: if responsibilities:
author_email = self.changes.get_latest_email_by_author(i) author_email = self.changes.get_latest_email_by_author(i)
@ -118,9 +117,9 @@ class ResponsibilitiesOutput(Outputable):
message_xml = "\t\t<message>" + _(RESPONSIBILITIES_INFO_TEXT) + "</message>\n" message_xml = "\t\t<message>" + _(RESPONSIBILITIES_INFO_TEXT) + "</message>\n"
resp_xml = "" resp_xml = ""
for i in sorted(set(i[0] for i in blame.get(self.hard, self.useweeks, self.changes).blames)): for i in sorted(set(i[0] for i in self.blame.blames)):
responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.changes, responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.changes,
self.hard, self.useweeks, i)), reverse=True) self.blame, i)), reverse=True)
if responsibilities: if responsibilities:
author_email = self.changes.get_latest_email_by_author(i) author_email = self.changes.get_latest_email_by_author(i)

View File

@ -26,10 +26,10 @@ class ResponsibiltyEntry(object):
class Responsibilities(object): class Responsibilities(object):
@staticmethod @staticmethod
def get(changes, hard, useweeks, author_name): def get(changes, blame, author_name):
author_blames = {} author_blames = {}
for i in blame.get(hard, useweeks, changes).blames.items(): for i in blame.blames.items():
if author_name == i[0][0]: if author_name == i[0][0]:
total_rows = i[1].rows - i[1].comments total_rows = i[1].rows - i[1].comments
if total_rows > 0: if total_rows > 0: