mirror of
https://github.com/ejwa/gitinspector.git
synced 2025-03-26 02:01:27 +01:00
Added per-tag functionality (needs better formatting)
This commit is contained in:
parent
a57d09a60d
commit
74d6f64333
4 changed files with 278 additions and 69 deletions
|
@ -123,7 +123,7 @@ class ChangesThread(threading.Thread):
|
|||
|
||||
def run(self):
|
||||
git_log_r = subprocess.Popen(filter(None, ["git", "log", "--reverse", "--pretty=%ct|%cd|%H|%aN|%aE",
|
||||
"--stat=100000,8192", "--no-merges", "-w", interval.get_since(),
|
||||
"--stat=100000,8192", "-w", interval.get_since(),
|
||||
interval.get_until(), "--date=short"] + (["-C", "-C", "-M"] if self.hard else []) +
|
||||
[self.first_hash + self.second_hash]), bufsize=1, stdout=subprocess.PIPE).stdout
|
||||
lines = git_log_r.readlines()
|
||||
|
@ -182,56 +182,110 @@ class Changes(object):
|
|||
authors_by_email = {}
|
||||
emails_by_author = {}
|
||||
|
||||
def __init__(self, repo, hard):
|
||||
self.commits = []
|
||||
interval.set_ref("HEAD");
|
||||
git_rev_list_p = subprocess.Popen(filter(None, ["git", "rev-list", "--reverse", "--no-merges",
|
||||
def __init__(self, repo, hard, tag, branch):
|
||||
if (tag != ""):
|
||||
self.commits = []
|
||||
interval.set_ref("HEAD");
|
||||
git_rev_list_p = subprocess.Popen(filter(None, ["git", "rev-list", "--reverse",
|
||||
interval.get_since(), interval.get_until(), tag]), bufsize=1,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
lines = git_rev_list_p.communicate()[0].splitlines()
|
||||
git_rev_list_p.stdout.close()
|
||||
|
||||
if git_rev_list_p.returncode == 0 and len(lines) > 0:
|
||||
progress_text = _(PROGRESS_TEXT)
|
||||
if repo != None:
|
||||
progress_text = "[%s] " % repo.name + progress_text
|
||||
|
||||
chunks = len(lines) // CHANGES_PER_THREAD
|
||||
self.commits = [None] * (chunks if len(lines) % CHANGES_PER_THREAD == 0 else chunks + 1)
|
||||
first_hash = ""
|
||||
|
||||
for i, entry in enumerate(lines):
|
||||
if i % CHANGES_PER_THREAD == CHANGES_PER_THREAD - 1:
|
||||
entry = entry.decode("utf-8", "replace").strip()
|
||||
second_hash = entry
|
||||
ChangesThread.create(hard, self, first_hash, second_hash, i)
|
||||
first_hash = entry + ".."
|
||||
|
||||
if format.is_interactive_format():
|
||||
terminal.output_progress(progress_text, i, len(lines))
|
||||
else:
|
||||
if CHANGES_PER_THREAD - 1 != i % CHANGES_PER_THREAD:
|
||||
entry = entry.decode("utf-8", "replace").strip()
|
||||
second_hash = entry
|
||||
ChangesThread.create(hard, self, first_hash, second_hash, i)
|
||||
|
||||
# Make sure all threads have completed.
|
||||
for i in range(0, NUM_THREADS):
|
||||
__thread_lock__.acquire()
|
||||
|
||||
# We also have to release them for future use.
|
||||
for i in range(0, NUM_THREADS):
|
||||
__thread_lock__.release()
|
||||
self.commits = [item for sublist in self.commits for item in sublist]
|
||||
|
||||
if len(self.commits) > 0:
|
||||
if interval.has_interval():
|
||||
interval.set_ref(self.commits[-1].sha)
|
||||
|
||||
self.first_commit_date = datetime.date(int(self.commits[0].date[0:4]), int(self.commits[0].date[5:7]),
|
||||
int(self.commits[0].date[8:10]))
|
||||
self.last_commit_date = datetime.date(int(self.commits[-1].date[0:4]), int(self.commits[-1].date[5:7]),
|
||||
int(self.commits[-1].date[8:10]))
|
||||
|
||||
elif (branch != ""):
|
||||
asdad
|
||||
else:
|
||||
self.commits = []
|
||||
interval.set_ref("HEAD");
|
||||
git_rev_list_p = subprocess.Popen(filter(None, ["git", "rev-list", "--reverse", "--no-merges",
|
||||
interval.get_since(), interval.get_until(), "HEAD"]), bufsize=1,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
lines = git_rev_list_p.communicate()[0].splitlines()
|
||||
git_rev_list_p.stdout.close()
|
||||
lines = git_rev_list_p.communicate()[0].splitlines()
|
||||
git_rev_list_p.stdout.close()
|
||||
|
||||
if git_rev_list_p.returncode == 0 and len(lines) > 0:
|
||||
progress_text = _(PROGRESS_TEXT)
|
||||
if repo != None:
|
||||
progress_text = "[%s] " % repo.name + progress_text
|
||||
if git_rev_list_p.returncode == 0 and len(lines) > 0:
|
||||
progress_text = _(PROGRESS_TEXT)
|
||||
if repo != None:
|
||||
progress_text = "[%s] " % repo.name + progress_text
|
||||
|
||||
chunks = len(lines) // CHANGES_PER_THREAD
|
||||
self.commits = [None] * (chunks if len(lines) % CHANGES_PER_THREAD == 0 else chunks + 1)
|
||||
first_hash = ""
|
||||
chunks = len(lines) // CHANGES_PER_THREAD
|
||||
self.commits = [None] * (chunks if len(lines) % CHANGES_PER_THREAD == 0 else chunks + 1)
|
||||
first_hash = ""
|
||||
|
||||
for i, entry in enumerate(lines):
|
||||
if i % CHANGES_PER_THREAD == CHANGES_PER_THREAD - 1:
|
||||
entry = entry.decode("utf-8", "replace").strip()
|
||||
second_hash = entry
|
||||
ChangesThread.create(hard, self, first_hash, second_hash, i)
|
||||
first_hash = entry + ".."
|
||||
for i, entry in enumerate(lines):
|
||||
if i % CHANGES_PER_THREAD == CHANGES_PER_THREAD - 1:
|
||||
entry = entry.decode("utf-8", "replace").strip()
|
||||
second_hash = entry
|
||||
ChangesThread.create(hard, self, first_hash, second_hash, i)
|
||||
first_hash = entry + ".."
|
||||
|
||||
if format.is_interactive_format():
|
||||
terminal.output_progress(progress_text, i, len(lines))
|
||||
if format.is_interactive_format():
|
||||
terminal.output_progress(progress_text, i, len(lines))
|
||||
else:
|
||||
if CHANGES_PER_THREAD - 1 != i % CHANGES_PER_THREAD:
|
||||
entry = entry.decode("utf-8", "replace").strip()
|
||||
second_hash = entry
|
||||
ChangesThread.create(hard, self, first_hash, second_hash, i)
|
||||
|
||||
# Make sure all threads have completed.
|
||||
for i in range(0, NUM_THREADS):
|
||||
__thread_lock__.acquire()
|
||||
# Make sure all threads have completed.
|
||||
for i in range(0, NUM_THREADS):
|
||||
__thread_lock__.acquire()
|
||||
|
||||
# We also have to release them for future use.
|
||||
for i in range(0, NUM_THREADS):
|
||||
__thread_lock__.release()
|
||||
# We also have to release them for future use.
|
||||
for i in range(0, NUM_THREADS):
|
||||
__thread_lock__.release()
|
||||
|
||||
self.commits = [item for sublist in self.commits for item in sublist]
|
||||
self.commits = [item for sublist in self.commits for item in sublist]
|
||||
|
||||
if len(self.commits) > 0:
|
||||
if interval.has_interval():
|
||||
interval.set_ref(self.commits[-1].sha)
|
||||
if len(self.commits) > 0:
|
||||
if interval.has_interval():
|
||||
interval.set_ref(self.commits[-1].sha)
|
||||
|
||||
self.first_commit_date = datetime.date(int(self.commits[0].date[0:4]), int(self.commits[0].date[5:7]),
|
||||
self.first_commit_date = datetime.date(int(self.commits[0].date[0:4]), int(self.commits[0].date[5:7]),
|
||||
int(self.commits[0].date[8:10]))
|
||||
self.last_commit_date = datetime.date(int(self.commits[-1].date[0:4]), int(self.commits[-1].date[5:7]),
|
||||
self.last_commit_date = datetime.date(int(self.commits[-1].date[0:4]), int(self.commits[-1].date[5:7]),
|
||||
int(self.commits[-1].date[8:10]))
|
||||
|
||||
def __iadd__(self, other):
|
||||
|
|
|
@ -23,6 +23,7 @@ import atexit
|
|||
import getopt
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from .blame import Blame
|
||||
from .changes import Changes
|
||||
from .config import GitConfig
|
||||
|
@ -37,6 +38,7 @@ from .output.filteringoutput import FilteringOutput
|
|||
from .output.metricsoutput import MetricsOutput
|
||||
from .output.responsibilitiesoutput import ResponsibilitiesOutput
|
||||
from .output.responsibility_per_file_output import Responsibility_Per_File_Output
|
||||
from .output.changes_per_tag_output import Changes_Per_Tag_Output
|
||||
from .output.timelineoutput import TimelineOutput
|
||||
|
||||
localization.init()
|
||||
|
@ -66,52 +68,108 @@ class Runner(object):
|
|||
terminal.skip_escapes(not sys.stdout.isatty())
|
||||
terminal.set_stdout_encoding()
|
||||
previous_directory = os.getcwd()
|
||||
summed_blames = Blame.__new__(Blame)
|
||||
summed_changes = Changes.__new__(Changes)
|
||||
summed_blames = []
|
||||
summed_changes = []
|
||||
tags = []
|
||||
branches = []
|
||||
summed_metrics = MetricsLogic.__new__(MetricsLogic)
|
||||
|
||||
for repo in repos:
|
||||
os.chdir(repo.location)
|
||||
repo = repo if len(repos) > 1 else None
|
||||
changes = Changes(repo, self.hard)
|
||||
summed_blames += Blame(repo, self.hard, self.useweeks, changes)
|
||||
summed_changes += changes
|
||||
|
||||
if self.include_metrics:
|
||||
summed_metrics += MetricsLogic()
|
||||
|
||||
if sys.stdout.isatty() and format.is_interactive_format():
|
||||
terminal.clear_row()
|
||||
else:
|
||||
os.chdir(previous_directory)
|
||||
if self.per_tag:
|
||||
os.chdir(repo.location)
|
||||
repo = repo if len(repos) > 1 else None
|
||||
#git list of tags
|
||||
process = subprocess.Popen(['git', 'tag'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
tags = process.communicate()[0].splitlines()
|
||||
for tag in tags:
|
||||
changes = Changes(repo, self.hard, tag, "")
|
||||
summed_blames.append(Blame(repo, self.hard, self.useweeks, changes))
|
||||
summed_changes.append(changes)
|
||||
|
||||
elif self.per_branch:
|
||||
os.chdir(repo.location)
|
||||
repo = repo if len(repos) > 1 else None
|
||||
#get list of branches
|
||||
|
||||
for branch in branches:
|
||||
changes = Changes(repo, self.hard, "", branch)
|
||||
summed_blames.append(Blame(repo, self.hard, self.useweeks, changes))
|
||||
summed_changes.append(changes)
|
||||
|
||||
else:
|
||||
os.chdir(repo.location)
|
||||
repo = repo if len(repos) > 1 else None
|
||||
summed_blames = Blame.__new__(Blame)
|
||||
summed_changes = Changes.__new__(Changes)
|
||||
changes = Changes(repo, self.hard, False, False)
|
||||
summed_blames += Blame(repo, self.hard, self.useweeks, changes)
|
||||
summed_changes += changes
|
||||
|
||||
if self.include_metrics:
|
||||
summed_metrics += MetricsLogic()
|
||||
if sys.stdout.isatty() and format.is_interactive_format():
|
||||
terminal.clear_row()
|
||||
else:
|
||||
os.chdir(previous_directory)
|
||||
|
||||
|
||||
format.output_header(repos)
|
||||
if (self.suppress == False):
|
||||
outputable.output(ChangesOutput(summed_changes))
|
||||
if self.per_tag:
|
||||
i = 0
|
||||
while (i < len(summed_changes)):
|
||||
if (self.suppress == False):
|
||||
outputable.output(Changes_Per_Tag_Output(summed_changes[i], tags[i]))
|
||||
if summed_changes[i].get_commits():
|
||||
if (self.suppress == False or self.blame == True):
|
||||
outputable.output(BlameOutput(summed_changes[i], summed_blames[i]))
|
||||
|
||||
if self.timeline:
|
||||
outputable.output(TimelineOutput(summed_changes[i], self.useweeks))
|
||||
|
||||
if summed_changes.get_commits():
|
||||
if self.blame:
|
||||
outputable.output(BlameOutput(summed_changes, summed_blames))
|
||||
if self.include_metrics:
|
||||
outputable.output(MetricsOutput(summed_metrics))
|
||||
|
||||
if self.responsibilities:
|
||||
outputable.output(ResponsibilitiesOutput(summed_changes[i], summed_blames[i]))
|
||||
|
||||
if self.per_file:
|
||||
outputable.output(Responsibility_Per_File_Output(summed_changes[i], summed_blames[i]))
|
||||
|
||||
if self.timeline:
|
||||
outputable.output(TimelineOutput(summed_changes, self.useweeks))
|
||||
outputable.output(FilteringOutput())
|
||||
|
||||
if self.list_file_types:
|
||||
outputable.output(ExtensionsOutput())
|
||||
|
||||
if self.include_metrics:
|
||||
outputable.output(MetricsOutput(summed_metrics))
|
||||
format.output_footer()
|
||||
os.chdir(previous_directory)
|
||||
i = i + 1
|
||||
else:
|
||||
if (self.suppress == False):
|
||||
outputable.output(ChangesOutput(summed_changes))
|
||||
|
||||
if self.responsibilities:
|
||||
outputable.output(ResponsibilitiesOutput(summed_changes, summed_blames))
|
||||
if summed_changes.get_commits():
|
||||
if (self.suppress == False or self.blame == True):
|
||||
outputable.output(BlameOutput(summed_changes, summed_blames))
|
||||
|
||||
if self.per_file:
|
||||
outputable.output(Responsibility_Per_File_Output(summed_changes, summed_blames))
|
||||
if self.timeline:
|
||||
outputable.output(TimelineOutput(summed_changes, self.useweeks))
|
||||
|
||||
outputable.output(FilteringOutput())
|
||||
if self.include_metrics:
|
||||
outputable.output(MetricsOutput(summed_metrics))
|
||||
|
||||
if self.list_file_types:
|
||||
outputable.output(ExtensionsOutput())
|
||||
if self.responsibilities:
|
||||
outputable.output(ResponsibilitiesOutput(summed_changes, summed_blames))
|
||||
|
||||
format.output_footer()
|
||||
os.chdir(previous_directory)
|
||||
if self.per_file:
|
||||
outputable.output(Responsibility_Per_File_Output(summed_changes, summed_blames))
|
||||
|
||||
outputable.output(FilteringOutput())
|
||||
|
||||
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):
|
||||
|
@ -147,7 +205,8 @@ def main():
|
|||
opts, args = optval.gnu_getopt(argv[1:], "f:F:hHlLmrTwxb:", ["exclude=", "file-types=", "format=",
|
||||
"hard:true", "help", "list-file-types:true", "localize-output:true",
|
||||
"metrics:true", "responsibilities:true", "since=", "grading:true",
|
||||
"timeline:true", "until=", "version", "weeks:true", "per-file:true", "blame:true", "suppress:false"])
|
||||
"timeline:true", "until=", "version", "weeks:true", "per-file:true", "blame:true", "suppress:false",
|
||||
"per-branch:false", "per-tag:false"])
|
||||
repos = __get_validated_git_repos__(set(args))
|
||||
|
||||
#We need the repos above to be set before we read the git config.
|
||||
|
|
96
gitinspector/output/changes_per_tag_output.py
Normal file
96
gitinspector/output/changes_per_tag_output.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
# 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
|
||||
|
||||
HISTORICAL_INFO_TEXT = N_("The following historical commit information, by author, for this tag:was found")
|
||||
NO_COMMITED_FILES_TEXT = N_("No commited files with the specified extensions were found")
|
||||
|
||||
class Changes_Per_Tag_Output(Outputable):
|
||||
def __init__(self, changes, tag):
|
||||
self.changes = changes
|
||||
self.tag = tag
|
||||
Outputable.__init__(self)
|
||||
|
||||
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\": \"" + "The following historical commit information, by author, for this tag:" + self.tag + " was found" + "\",\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, tag):
|
||||
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("The following historical commit information, by author, for this tag:" + self.tag + " was found" + ":", 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))
|
||||
|
||||
for i in sorted(authorinfo_list):
|
||||
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))
|
||||
else:
|
||||
print(_(NO_COMMITED_FILES_TEXT) + ".")
|
|
@ -72,7 +72,7 @@ class Responsibility_Per_File_Output(Outputable):
|
|||
resp_json += "\t\t\t\t\t\"author name\": \"" + author_dict["author_name"] + "\",\n"
|
||||
resp_json += "\t\t\t\t\t\"author email\": \"" + author_dict["author_email"] + "\", \n"
|
||||
resp_json += "\t\t\t\t\t\"rows owned\": " + str(author_dict["rows_owned"]) + ",\n"
|
||||
resp_json += "\t\t\t\t\t\"perctenage of document\": " + str(float(author_dict["rows_owned"])/float(total_rows_dict[file])) + "\n"
|
||||
resp_json += "\t\t\t\t\t\"perctenage of document\": " + str(float(author_dict["rows_owned"])/float(total_rows_dict[file])*100) + "\n"
|
||||
resp_json += "\t\t\t\t},"
|
||||
|
||||
#next 3 lines are diddo from responsibilitiesoutput.py
|
||||
|
|
Loading…
Add table
Reference in a new issue