1
0
Fork 0
mirror of https://github.com/ejwa/gitinspector.git synced 2025-03-26 02:01:27 +01:00
This commit is contained in:
de2sl2pfds 2020-10-19 18:34:31 +00:00 committed by GitHub
commit 174a88b8f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 381 additions and 18 deletions

View file

@ -27,12 +27,14 @@ import zipfile
from .localization import N_
from . import basedir, localization, terminal, version
__available_formats__ = ["html", "htmlembedded", "json", "text", "xml"]
__available_formats__ = ["html", "htmlembedded", "json", "text", "xml", "csv"]
DEFAULT_FORMAT = __available_formats__[3]
__selected_format__ = DEFAULT_FORMAT
__selected_format_tag__ = ""
class InvalidFormatError(Exception):
def __init__(self, msg):
super(InvalidFormatError, self).__init__(msg)
@ -47,6 +49,17 @@ def select(format):
def get_selected():
return __selected_format__
def set_tag(format):
global __selected_format_tag__
__selected_format_tag__ = format
def get_tag():
return __selected_format_tag__
def is_interactive_format():
return __selected_format__ == "text"

44
gitinspector/gitinspector.py Normal file → Executable file
View file

@ -32,6 +32,7 @@ from . import (basedir, clone, extensions, filtering, format, help, interval,
from .output import outputable
from .output.blameoutput import BlameOutput
from .output.changesoutput import ChangesOutput
from .output.changesblameoutput import ChangesBlameOutput
from .output.extensionsoutput import ExtensionsOutput
from .output.filteringoutput import FilteringOutput
from .output.metricsoutput import MetricsOutput
@ -79,29 +80,34 @@ class Runner(object):
else:
os.chdir(previous_directory)
format.output_header(repos)
outputable.output(ChangesOutput(summed_changes))
# print("current format: " + format.get_selected())
if format.get_selected() == "csv":
# print("output format: " + format.get_selected())
outputable.output(ChangesBlameOutput(summed_changes, summed_blames))
else:
format.output_header(repos)
outputable.output(ChangesOutput(summed_changes))
if summed_changes.get_commits():
outputable.output(BlameOutput(summed_changes, summed_blames))
if summed_changes.get_commits():
outputable.output(BlameOutput(summed_changes, summed_blames))
if self.timeline:
outputable.output(TimelineOutput(summed_changes, self.useweeks))
if self.timeline:
outputable.output(TimelineOutput(summed_changes, self.useweeks))
if self.include_metrics:
outputable.output(MetricsOutput(summed_metrics))
if self.include_metrics:
outputable.output(MetricsOutput(summed_metrics))
if self.responsibilities:
outputable.output(ResponsibilitiesOutput(summed_changes, summed_blames))
if self.responsibilities:
outputable.output(ResponsibilitiesOutput(summed_changes, summed_blames))
outputable.output(FilteringOutput())
outputable.output(FilteringOutput())
if self.list_file_types:
outputable.output(ExtensionsOutput())
format.output_footer()
if self.list_file_types:
outputable.output(ExtensionsOutput())
format.output_footer()
os.chdir(previous_directory)
def __check_python_version__():
if sys.version_info < (2, 6):
python_version = str(sys.version_info[0]) + "." + str(sys.version_info[1])
@ -133,12 +139,13 @@ def main():
repos = []
try:
opts, args = optval.gnu_getopt(argv[1:], "f:F:hHlLmrTwx:", ["exclude=", "file-types=", "format=",
opts, args = optval.gnu_getopt(argv[1:], "f:t:F:hHlLmrTwx:", ["exclude=", "file-types=", "tag=" ,"format=",
"hard:true", "help", "list-file-types:true", "localize-output:true",
"metrics:true", "responsibilities:true", "since=", "grading:true",
"timeline:true", "until=", "version", "weeks:true"])
# print("begin git clone")
repos = __get_validated_git_repos__(set(args))
# print("end git clone")
#We need the repos above to be set before we read the git config.
GitConfig(run, repos[-1].location).read()
clear_x_on_next_pass = True
@ -149,6 +156,8 @@ def main():
sys.exit(0)
elif o in("-f", "--file-types"):
extensions.define(a)
elif o in("-t", "--tag"):
format.set_tag(a)
elif o in("-F", "--format"):
if not format.select(a):
raise format.InvalidFormatError(_("specified output format not supported."))
@ -213,6 +222,7 @@ def main():
@atexit.register
def cleanup():
clone.delete()
# pass
if __name__ == "__main__":
main()

View file

@ -0,0 +1,239 @@
# coding: utf-8
#
# Copyright © 2012-2015 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/>.
from __future__ import print_function
from __future__ import unicode_literals
import json
import textwrap
from ..localization import N_
from .. import format, gravatar, terminal
from .outputable import Outputable
from ..blame import Blame
HISTORICAL_INFO_TEXT = N_("The following historical commit information, by author, was found")
NO_COMMITED_FILES_TEXT = N_("No commited files with the specified extensions were found")
class ChangesBlameOutput(Outputable):
def __init__(self, changes, blame):
if format.is_interactive_format():
print("")
self.changes = changes
self.blame = blame
Outputable.__init__(self)
def output_html(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
changes_xml = "<div><div class=\"box\">"
chart_data = ""
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
changes_xml += "<p>" + _(HISTORICAL_INFO_TEXT) + ".</p><div><table id=\"changes\" class=\"git\">"
changes_xml += "<thead><tr> <th>{0}</th> <th>{1}</th> <th>{2}</th> <th>{3}</th> <th>{4}</th>".format(
_("Author"), _("Commits"), _("Insertions"), _("Deletions"), _("% of changes"))
changes_xml += "</tr></thead><tbody>"
for i, entry in enumerate(sorted(authorinfo_list)):
authorinfo = authorinfo_list.get(entry)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
changes_xml += "<tr " + ("class=\"odd\">" if i % 2 == 1 else ">")
if format.get_selected() == "html":
changes_xml += "<td><img src=\"{0}\"/>{1}</td>".format(
gravatar.get_url(self.changes.get_latest_email_by_author(entry)), entry)
else:
changes_xml += "<td>" + entry + "</td>"
changes_xml += "<td>" + str(authorinfo.commits) + "</td>"
changes_xml += "<td>" + str(authorinfo.insertions) + "</td>"
changes_xml += "<td>" + str(authorinfo.deletions) + "</td>"
changes_xml += "<td>" + "{0:.2f}".format(percentage) + "</td>"
changes_xml += "</tr>"
chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry), "{0:.2f}".format(percentage))
if sorted(authorinfo_list)[-1] != entry:
chart_data += ", "
changes_xml += ("<tfoot><tr> <td colspan=\"5\">&nbsp;</td> </tr></tfoot></tbody></table>")
changes_xml += "<div class=\"chart\" id=\"changes_chart\"></div></div>"
changes_xml += "<script type=\"text/javascript\">"
changes_xml += " changes_plot = $.plot($(\"#changes_chart\"), [{0}], {{".format(chart_data)
changes_xml += " series: {"
changes_xml += " pie: {"
changes_xml += " innerRadius: 0.4,"
changes_xml += " show: true,"
changes_xml += " combine: {"
changes_xml += " threshold: 0.01,"
changes_xml += " label: \"" + _("Minor Authors") + "\""
changes_xml += " }"
changes_xml += " }"
changes_xml += " }, grid: {"
changes_xml += " hoverable: true"
changes_xml += " }"
changes_xml += " });"
changes_xml += "</script>"
else:
changes_xml += "<p>" + _(NO_COMMITED_FILES_TEXT) + ".</p>"
changes_xml += "</div></div>"
print(changes_xml)
def output_json(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
message_json = "\t\t\t\"message\": \"" + _(HISTORICAL_INFO_TEXT) + "\",\n"
changes_json = ""
for i in sorted(authorinfo_list):
author_email = self.changes.get_latest_email_by_author(i)
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
name_json = "\t\t\t\t\"name\": \"" + i + "\",\n"
email_json = "\t\t\t\t\"email\": \"" + author_email + "\",\n"
gravatar_json = "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(author_email) + "\",\n"
commits_json = "\t\t\t\t\"commits\": " + str(authorinfo.commits) + ",\n"
insertions_json = "\t\t\t\t\"insertions\": " + str(authorinfo.insertions) + ",\n"
deletions_json = "\t\t\t\t\"deletions\": " + str(authorinfo.deletions) + ",\n"
percentage_json = "\t\t\t\t\"percentage_of_changes\": " + "{0:.2f}".format(percentage) + "\n"
changes_json += ("{\n" + name_json + email_json + gravatar_json + commits_json +
insertions_json + deletions_json + percentage_json + "\t\t\t}")
changes_json += ","
else:
changes_json = changes_json[:-1]
print("\t\t\"changes\": {\n" + message_json + "\t\t\t\"authors\": [\n\t\t\t" + changes_json + "]\n\t\t}", end="")
else:
print("\t\t\"exception\": \"" + _(NO_COMMITED_FILES_TEXT) + "\"")
def output_text(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
print(textwrap.fill(_(HISTORICAL_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n")
terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Commits"), 13) +
terminal.rjust(_("Insertions"), 14) + terminal.rjust(_("Deletions"), 15) +
terminal.rjust(_("% of changes"), 16) +
terminal.rjust(_("Author"), 21) + terminal.rjust(_("Rows"), 10) + terminal.rjust(_("Stability"),
15) +
terminal.rjust(_("Age"), 13) + terminal.rjust(_("% in comments"), 20))
for i,j in zip(sorted(authorinfo_list),sorted(self.blame.get_summed_blames().items())):
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
print(terminal.ljust(i, 20)[0:20 - terminal.get_excess_column_count(i)], end=" ")
print(str(authorinfo.commits).rjust(13), end=" ")
print(str(authorinfo.insertions).rjust(13), end=" ")
print(str(authorinfo.deletions).rjust(14), end=" ")
print("{0:.2f}".format(percentage).rjust(15), end=" ")
# blame
#print(terminal.ljust(j[0], 20)[0:20 - terminal.get_excess_column_count(j[0])], end=" ")
print(j[0].rjust(21), end=" ")
print(str(j[1].rows).rjust(10), end=" ")
print("{0:.1f}".format(Blame.get_stability(j[0], j[1].rows, self.changes)).rjust(14), end=" ")
print("{0:.1f}".format(float(j[1].skew) / j[1].rows).rjust(12), end=" ")
print("{0:.2f}".format(100.0 * j[1].comments / j[1].rows).rjust(19))
else:
print(_(NO_COMMITED_FILES_TEXT) + ".")
def output_csv(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
#format.get_tag().split(',')
tagstr = format.get_tag()
if tagstr <> "":
tagstr += ","
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
# print(textwrap.fill(_(HISTORICAL_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n")
terminal.printb(tagstr + "Author,Commits,Insertions,Deletions,% of changes,Author,Rows,Stability,Age,% in comments")
for i,j in zip(sorted(authorinfo_list),sorted(self.blame.get_summed_blames().items())):
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
line = i \
+ "," + str(authorinfo.commits) \
+ "," + str(authorinfo.insertions) \
+ "," + str(authorinfo.deletions) \
+ "," + str(percentage) \
+ "," + j[0] \
+ "," + str(j[1].rows) \
+ "," + str(Blame.get_stability(j[0], j[1].rows, self.changes)) \
+ "," + str(float(j[1].skew) / j[1].rows) \
+ "," + str(100.0 * j[1].comments / j[1].rows)
print(tagstr + line)
else:
print(_(NO_COMMITED_FILES_TEXT) + ".")
def output_xml(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
message_xml = "\t\t<message>" + _(HISTORICAL_INFO_TEXT) + "</message>\n"
changes_xml = ""
for i in sorted(authorinfo_list):
author_email = self.changes.get_latest_email_by_author(i)
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
name_xml = "\t\t\t\t<name>" + i + "</name>\n"
email_xml = "\t\t\t\t<email>" + author_email + "</email>\n"
gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(author_email) + "</gravatar>\n"
commits_xml = "\t\t\t\t<commits>" + str(authorinfo.commits) + "</commits>\n"
insertions_xml = "\t\t\t\t<insertions>" + str(authorinfo.insertions) + "</insertions>\n"
deletions_xml = "\t\t\t\t<deletions>" + str(authorinfo.deletions) + "</deletions>\n"
percentage_xml = "\t\t\t\t<percentage-of-changes>" + "{0:.2f}".format(percentage) + "</percentage-of-changes>\n"
changes_xml += ("\t\t\t<author>\n" + name_xml + email_xml + gravatar_xml + commits_xml +
insertions_xml + deletions_xml + percentage_xml + "\t\t\t</author>\n")
print("\t<changes>\n" + message_xml + "\t\t<authors>\n" + changes_xml + "\t\t</authors>\n\t</changes>")
else:
print("\t<changes>\n\t\t<exception>" + _(NO_COMMITED_FILES_TEXT) + "</exception>\n\t</changes>")

View file

@ -119,3 +119,18 @@ class FilteringOutput(Outputable):
FilteringOutput.__output_xml_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1], "emails")
FilteringOutput.__output_xml_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1], "revision")
print("\t</filtering>")
@staticmethod
def __output_csv_section__(info_string, filtered):
if filtered:
print("\n" + textwrap.fill(info_string + ":", width=terminal.get_size()[0]))
for i in filtered:
(width, _unused) = terminal.get_size()
print("...%s" % i[-width+3:] if len(i) > width else i)
def output_csv(self):
FilteringOutput.__output_csv_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1])
FilteringOutput.__output_csv_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1])
FilteringOutput.__output_csv_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1])
FilteringOutput.__output_csv_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1])

View file

@ -34,6 +34,9 @@ class Outputable(object):
def output_xml(self):
raise NotImplementedError(_("XML output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output_csv(self):
raise NotImplementedError(_("CSV output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output(outputable):
if format.get_selected() == "html" or format.get_selected() == "htmlembedded":
outputable.output_html()
@ -41,5 +44,7 @@ def output(outputable):
outputable.output_json()
elif format.get_selected() == "text":
outputable.output_text()
elif format.get_selected() == "csv":
outputable.output_csv()
else:
outputable.output_xml()

81
gitreport.py Normal file
View file

@ -0,0 +1,81 @@
import os
import re
import sys
import getopt
import datetime
helpinfo = ("""Usage: gitreport [OPTION]...
-r, --repo=<repo configfile> a space separated list of git repositories
line formart likes:
tag repository since until
gitinspectory https://github.com/ejwa/gitinspector.git 2018-01-01 2018-12-01
gitinspectorycvs https://github.com/de2sl2pfds/gitinspector.git 2018-01-01 2018-12-01
-o, --output=<output file> output filename of csv format
-h, --help display this help and exit
""")
def report(repo,output):
if not os.path.isfile(repo):
print("repo config file not exists")
sys.exit(-3)
if os.path.isfile(output):
output = output + "." + datetime.datetime.today().strftime('%Y%m%d%H%M%S')
print("output file exists. new file name is : " + output)
else:
print("output file name is : " + output)
os.system("> " + output)
i = 0
for line in open(repo):
a = re.compile("\s+").split(line.strip())
if a[0].lower().strip() == 'tag':
print("title: " + line.strip())
continue
if len(a) > 3 and a[1].lower().find('.git') > -1:
print("No.[" + str(i) + "] : " + line.strip())
print(" repository info: tag=" + a[0] + " since=" + a[2] + " until=" + a[3] + " gitrepo=" + a[1])
cmd = ("python gitinspector.py --since="+a[2]+" --until="+a[3]+" --tag="+a[0]+" -F csv "+ a[1] + ">> " + output)
print(" command: " + cmd)
os.system(cmd)
i += 1
print("")
if i < 1:
print("no repository found.")
os.unlink(output)
else:
print("processed " + str(i) + " repositories.")
def main(argv):
repofile = "repo.txt"
outputfile = "report.csv"
try:
opts, args = getopt.getopt(argv, "h:r:o:",
["help=","repo=","output="])
except getopt.GetoptError:
print helpinfo
sys.exit(-1)
if len(argv) == 0:
print helpinfo
sys.exit(-2)
for o, a in opts:
if o in("-h", "--help"):
print(helpinfo)
sys.exit(0)
elif o in("-r", "--repo"):
repofile = a
elif o in("-o", "--output"):
outputfile = a
else:
print(helpinfo)
sys.exit(0)
report(repofile,outputfile)
if __name__ == "__main__":
main(sys.argv[1:])