# coding: utf-8 # # Copyright © 2012-2013 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 . from __future__ import print_function from outputable import Outputable import extensions import filtering import re import os import subprocess import terminal import textwrap import sys class FileDiff: def __init__(self, string): commit_line = string.split("|") if commit_line.__len__() == 2: self.name = commit_line[0].strip() self.insertions = commit_line[1].count("+") self.deletions = commit_line[1].count("-") @staticmethod def is_filediff_line(string): string = string.split("|") return string.__len__() == 2 and string[1].find("Bin") == -1 and ('+' in string[1] or '-' in string[1]) @staticmethod def get_extension(string): string = string.split("|")[0].strip().strip("{}").strip("\"").strip("'") string = string.decode("string_escape").strip() return os.path.splitext(string)[1][1:] @staticmethod def get_filename(string): string = string.split("|")[0].strip().strip("{}").strip("\"").strip("'") return string.decode("string_escape").strip() @staticmethod def is_valid_extension(string): extension = FileDiff.get_extension(string) for i in extensions.get(): if extension == i: return True return False class Commit: def __init__(self, string): self.filediffs = [] commit_line = string.split("|") if commit_line.__len__() == 4: self.date = commit_line[0] self.sha = commit_line[1] self.author = re.sub("[^\w ]", "", commit_line[2].strip()) self.message = commit_line[3].strip() def add_filediff(self, filediff): self.filediffs.append(filediff) def get_filediffs(self): return self.filediffs @staticmethod def is_commit_line(string): return string.split("|").__len__() == 4 class AuthorInfo: insertions = 0 deletions = 0 commits = 0 class Changes: def __init__(self, hard): self.commits = [] git_log_r = subprocess.Popen("git log --pretty='%ad|%t|%aN|%s' --stat=100000,8192 --no-merges -w " + "{0} --date=short".format("-C -C -M" if hard else ""), shell=True, bufsize=1, stdout=subprocess.PIPE).stdout commit = None found_valid_extension = False lines = git_log_r.readlines() for i in lines: i = i.decode("utf-8", "replace") if Commit.is_commit_line(i) or i == lines[-1]: if found_valid_extension: self.commits.append(commit) found_valid_extension = False commit = Commit(i) if FileDiff.is_filediff_line(i) and not filtering.set_filtered(FileDiff.get_filename(i)): extensions.add_located(FileDiff.get_extension(i)) if FileDiff.is_valid_extension(i): found_valid_extension = True filediff = FileDiff(i) commit.add_filediff(filediff) def get_commits(self): return self.commits @staticmethod def __modify_authorinfo__(authors, key, commit): if authors.get(key, None) == None: authors[key] = AuthorInfo() if commit.get_filediffs(): authors[key].commits += 1 for j in commit.get_filediffs(): authors[key].insertions += j.insertions authors[key].deletions += j.deletions def get_authorinfo_list(self): authors = {} for i in self.commits: Changes.__modify_authorinfo__(authors, i.author, i) return authors def get_authordateinfo_list(self): authors = {} for i in self.commits: Changes.__modify_authorinfo__(authors, (i.date, i.author), i) return authors __changes__ = None def get(hard): global __changes__ if __changes__ == None: __changes__ = Changes(hard) return __changes__ __historical_info_text__ = "The following historical commit information, by author, was found in the repository" __no_commited_files__ = "No commited files with the specified extensions were found" class ChangesOutput(Outputable): def __init__(self, hard): self.hard = hard Outputable.__init__(self) def output_html(self): authorinfo_list = get(self.hard).get_authorinfo_list() total_changes = 0.0 changes_xml = "
" 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 += "

" + __historical_info_text__ + ".

" changes_xml += ("" + "") changes_xml += "" 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 += "" if i % 2 == 1 else ">") changes_xml += "" changes_xml += "" changes_xml += "" changes_xml += "" changes_xml += "" changes_xml += "" chart_data += "{{label: \"{0}\", data: {1}}}".format(entry, "{0:.2f}".format(percentage)) if sorted(authorinfo_list)[-1] != entry: chart_data += ", " changes_xml += ("" + "
Author Commits Insertions Deletions% of changes
" + entry + "" + str(authorinfo.commits) + "" + str(authorinfo.insertions) + "" + str(authorinfo.deletions) + "" + "{0:.2f}".format(percentage) + "
         
") changes_xml += "
" changes_xml += "" else: changes_xml += "

" + __no_commited_files__ + ".

" changes_xml += "
" print(changes_xml) def output_text(self): authorinfo_list = get(self.hard).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("Author".ljust(21) + "Commits " + "Insertions " + "Deletions " + "% of changes") 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(i.ljust(20)[0:20], end=" ") print(str(authorinfo.commits).rjust(7), end=" ") print(str(authorinfo.insertions).rjust(12), end=" ") print(str(authorinfo.deletions).rjust(11), end=" ") print("{0:.2f}".format(percentage).rjust(14)) else: print(__no_commited_files__ + ".") def output_xml(self): authorinfo_list = get(self.hard).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" + __historical_info_text__ + "\n" changes_xml = "" 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 name_xml = "\t\t\t\t" + i + "\n" commits_xml = "\t\t\t\t" + str(authorinfo.commits) + "\n" insertions_xml = "\t\t\t\t" + str(authorinfo.insertions) + "\n" deletions_xml = "\t\t\t\t" + str(authorinfo.deletions) + "\n" percentage_xml = "\t\t\t\t" + "{0:.2f}".format(percentage) + "\n" changes_xml += ("\t\t\t\n" + name_xml + commits_xml + insertions_xml + deletions_xml + percentage_xml + "\t\t\t\n") print("\t\n" + message_xml + "\t\t\n" + changes_xml + "\t\t\n\t") else: print("\t\n\t\t" + __no_commited_files__ + "\n\t")