diff --git a/gitinspector/blame.py b/gitinspector/blame.py index b76c9c4..27e087e 100644 --- a/gitinspector/blame.py +++ b/gitinspector/blame.py @@ -47,10 +47,11 @@ __thread_lock__ = threading.BoundedSemaphore(NUM_THREADS) __blame_lock__ = threading.Lock() class BlameThread(threading.Thread): - def __init__(self, blame_string, extension, blames, filename): + def __init__(self, changes, blame_string, extension, blames, filename): __thread_lock__.acquire() # Lock controlling the number of threads running threading.Thread.__init__(self) + self.changes = changes self.blame_string = blame_string self.extension = extension self.blames = blames @@ -63,16 +64,19 @@ class BlameThread(threading.Thread): for j in git_blame_r.readlines(): j = j.decode("utf-8", "replace") if Blame.is_blame_line(j): - author_mail = Blame.get_author_mail(j) + email = Blame.get_author_email(j) + author = self.changes.get_latest_author_by_email(email) content = Blame.get_content(j) __blame_lock__.acquire() # Global lock used to protect calls from here... - if self.blames.get((author_mail, self.filename), None) == None: - self.blames[(author_mail, self.filename)] = BlameEntry() + if not filtering.set_filtered(author, "author") and not filtering.set_filtered(email, "email"): + if self.blames.get((author, self.filename), None) == None: + self.blames[(author, self.filename)] = BlameEntry() + + (comments, is_inside_comment) = comment.handle_comment_block(is_inside_comment, self.extension, content) + self.blames[(author, self.filename)].comments += comments + self.blames[(author, self.filename)].rows += 1 - (comments, is_inside_comment) = comment.handle_comment_block(is_inside_comment, self.extension, content) - self.blames[(author_mail, self.filename)].comments += comments - self.blames[(author_mail, self.filename)].rows += 1 __blame_lock__.release() # ...to here. git_blame_r.close() @@ -81,7 +85,7 @@ class BlameThread(threading.Thread): PROGRESS_TEXT = N_("Checking how many rows belong to each author (Progress): {0:.0f}%") class Blame: - def __init__(self, hard): + def __init__(self, hard, changes): self.blames = {} ls_tree_r = subprocess.Popen("git ls-tree --name-only -r " + interval.get_ref(), shell=True, bufsize=1, stdout=subprocess.PIPE).stdout @@ -94,9 +98,9 @@ class Blame: if FileDiff.is_valid_extension(row) and not filtering.set_filtered(FileDiff.get_filename(row)): if not missing.add(row): - blame_string = "git blame -w {0} ".format("-C -C -M" if hard else "") + \ + blame_string = "git blame -e -w {0} ".format("-C -C -M" if hard else "") + \ interval.get_since() + interval.get_ref() + " -- \"" + row + "\"" - thread = BlameThread(blame_string, FileDiff.get_extension(row), self.blames, row.strip()) + thread = BlameThread(changes, blame_string, FileDiff.get_extension(row), self.blames, row.strip()) thread.daemon = True thread.start() @@ -119,9 +123,9 @@ class Blame: return string.find(" (") != -1 @staticmethod - def get_author_mail(string): - author_mail = re.search(" \((.*?)\d\d\d\d-\d\d-\d\d", string) - return author_mail.group(1).strip().lstrip("<").rstrip(">") + def get_author_email(string): + author_email = re.search(" \((.*?)\d\d\d\d-\d\d-\d\d", string) + return author_email.group(1).strip().lstrip("<").rstrip(">") @staticmethod def get_content(string): @@ -141,10 +145,10 @@ class Blame: __blame__ = None -def get(hard): +def get(hard, changes): global __blame__ if __blame__ == None: - __blame__ = Blame(hard) + __blame__ = Blame(hard, changes) return __blame__ @@ -155,11 +159,10 @@ class BlameOutput(Outputable): def __init__(self, hard): self.hard = hard self.changes = changes.get(hard) + get(self.hard, self.changes) Outputable.__init__(self) def output_html(self): - get(self.hard) - blame_xml = "<div><div class=\"box\">" blame_xml += "<p>" + _(BLAME_INFO_TEXT) + ".</p><div><table id=\"blame\" class=\"git\">" blame_xml += "<thead><tr> <th>{0}</th> <th>{1}</th> <th>{2}</th> </tr></thead>".format(_("Author"), @@ -177,7 +180,7 @@ class BlameOutput(Outputable): blame_xml += "<tr " + ("class=\"odd\">" if i % 2 == 1 else ">") if format.get_selected() == "html": - author_email = self.changes.get_author_email(entry[0]) + author_email = self.changes.get_latest_email_by_author(entry[0]) blame_xml += "<td><img src=\"{0}\"/>{1}</td>".format(gravatar.get_url(author_email), entry[0]) else: blame_xml += "<td>" + entry[0] + "</td>" @@ -214,7 +217,6 @@ class BlameOutput(Outputable): def output_text(self): print("") - get(self.hard) if self.hard and sys.stdout.isatty(): terminal.clear_row() @@ -228,13 +230,11 @@ class BlameOutput(Outputable): print("{0:.2f}".format(100.0 * i[1].comments / i[1].rows).rjust(19)) def output_xml(self): - get(self.hard) - message_xml = "\t\t<message>" + _(BLAME_INFO_TEXT) + "</message>\n" blame_xml = "" for i in sorted(__blame__.get_summed_blames().items()): - author_email = self.changes.get_author_email(i[0]) + author_email = self.changes.get_latest_email_by_author(i[0]) 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" diff --git a/gitinspector/changes.py b/gitinspector/changes.py index 8f87e33..ec5e188 100644 --- a/gitinspector/changes.py +++ b/gitinspector/changes.py @@ -100,7 +100,8 @@ class AuthorInfo: class Changes: authors = {} authors_dateinfo = {} - authors_email = {} + authors_by_email = {} + emails_by_author = {} def __init__(self, hard): self.commits = [] @@ -119,7 +120,8 @@ class Changes: if Commit.is_commit_line(j): (author, email) = Commit.get_author_and_email(j) - self.authors_email[author] = email + self.emails_by_author[author] = email + self.authors_by_email[email] = author if Commit.is_commit_line(j) or i is lines[-1]: if found_valid_extension: @@ -128,7 +130,8 @@ class Changes: found_valid_extension = False commit = Commit(j) - if FileDiff.is_filediff_line(j) and not filtering.set_filtered(FileDiff.get_filename(j)): + if FileDiff.is_filediff_line(j) and not filtering.set_filtered(FileDiff.get_filename(j)) and not \ + filtering.set_filtered(commit.author, "author") and not filtering.set_filtered(commit.email, "email"): extensions.add_located(FileDiff.get_extension(j)) if FileDiff.is_valid_extension(j): @@ -167,8 +170,11 @@ class Changes: return self.authors_dateinfo - def get_author_email(self, name): - return self.authors_email[name] + def get_latest_author_by_email(self, name): + return self.authors_by_email[name] + + def get_latest_email_by_author(self, name): + return self.emails_by_author[name] __changes__ = None @@ -211,7 +217,7 @@ class ChangesOutput(Outputable): if format.get_selected() == "html": changes_xml += "<td><img src=\"{0}\"/>{1}</td>".format( - gravatar.get_url(self.changes.get_author_email(entry)), entry) + gravatar.get_url(self.changes.get_latest_email_by_author(entry)), entry) else: changes_xml += "<td>" + entry + "</td>" @@ -290,7 +296,7 @@ class ChangesOutput(Outputable): 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" - gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(self.changes.get_author_email(i)) + "</gravatar>\n" + gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(self.changes.get_latest_email_by_author(i)) + "</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" diff --git a/gitinspector/filtering.py b/gitinspector/filtering.py index ad2cc4b..af4bbae 100644 --- a/gitinspector/filtering.py +++ b/gitinspector/filtering.py @@ -25,8 +25,7 @@ import re import terminal import textwrap -__filters__ = [] -__filtered_files__ = set() +__filters__ = {"file": [[], set()], "author": [[], set()], "email": [[], set()]} class InvalidRegExpError(ValueError): def __init__(self, msg): @@ -37,56 +36,98 @@ def get(): return __filters__ def add(string): - __filters__.append(string) + global __filters__ + for i in __filters__: + if (i + ":").lower() == string[0:len(i) + 1].lower(): + __filters__[i][0].append(string[len(i) + 1:]) + return + __filters__["file"][0].append(string) def clear(): global __filters__ - __filters__ = [] + for i in __filters__: + __filters__[i][0] = [] -def get_filered(): - return __filtered_files__ +def get_filered(filter_type="file"): + return __filters__[filter_type][1] -def set_filtered(file_name): - string = file_name.strip() +def has_filtered(): + for i in __filters__: + if __filters__[i][1]: + return True + return False + +def set_filtered(string, filter_type="file"): + string = string.strip() if len(string) > 0: - for i in __filters__: + for i in __filters__[filter_type][0]: try: if re.search(i, string) != None: - __filtered_files__.add(string) + __filters__[filter_type][1].add(string) return True except: raise InvalidRegExpError(_("invalid regular expression specified")) return False FILTERING_INFO_TEXT = N_("The following files were excluded from the statistics due to the specified exclusion patterns") +FILTERING_AUTHOR_INFO_TEXT = N_("The following authors were excluded from the statistics due to the specified exclusion patterns") +FILTERING_EMAIL_INFO_TEXT = N_("The authors with the following emails were excluded from the statistics due to the specified exclusion patterns") class Filtering(Outputable): - def output_html(self): - if __filtered_files__: - filtering_xml = "<div><div class=\"box\">" - filtering_xml += "<p>" + _(FILTERING_INFO_TEXT) + "."+ "</p>" + @staticmethod + def __output_html_section__(info_string, filtered): + filtering_xml = "" - for i in __filtered_files__: + if filtered: + filtering_xml += "<p>" + info_string + "."+ "</p>" + + for i in filtered: filtering_xml += "<p>" + i + "</p>" + return filtering_xml + + def output_html(self): + if has_filtered(): + filtering_xml = "<div><div class=\"box\">" + Filtering.__output_html_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1]); + Filtering.__output_html_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1]); + Filtering.__output_html_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1]); filtering_xml += "</div></div>" + print(filtering_xml) - def output_text(self): - if __filtered_files__: - print("\n" + textwrap.fill(_(FILTERING_INFO_TEXT) + ":", width=terminal.get_size()[0])) + @staticmethod + def __output_text_section__(info_string, filtered): + if filtered: + print("\n" + textwrap.fill(info_string + ":", width=terminal.get_size()[0])) - for i in __filtered_files__: + for i in filtered: (width, _unused) = terminal.get_size() print("...%s" % i[-width+3:] if len(i) > width else i) - def output_xml(self): - if __filtered_files__: - message_xml = "\t\t<message>" + _(FILTERING_INFO_TEXT) + "</message>\n" + def output_text(self): + Filtering.__output_text_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1]); + Filtering.__output_text_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1]); + Filtering.__output_text_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1]); + + @staticmethod + def __output_xml_section__(info_string, filtered, container_tagname): + if filtered: + message_xml = "\t\t\t<message>" +info_string + "</message>\n" filtering_xml = "" - for i in __filtered_files__: - filtering_xml += "\t\t\t<file>" + i + "</file>\n" + for i in filtered: + filtering_xml += "\t\t\t\t<entry>".format(container_tagname) + i + "</entry>\n".format(container_tagname) - print("\t<filering>\n" + message_xml + "\t\t<files>\n" + filtering_xml + "\t\t</files>\n\t</filtering>") + print("\t\t<{0}>".format(container_tagname)) + print(message_xml + "\t\t\t<entries>\n" + filtering_xml + "\t\t\t</entries>\n") + print("\t\t</{0}>".format(container_tagname)) + + def output_xml(self): + if has_filtered(): + print("\t<filtering>") + Filtering.__output_xml_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1], "files"); + Filtering.__output_xml_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1], "authors"); + Filtering.__output_xml_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1], "emails"); + print("\t</filtering>") diff --git a/gitinspector/responsibilities.py b/gitinspector/responsibilities.py index deb3d18..244c277 100644 --- a/gitinspector/responsibilities.py +++ b/gitinspector/responsibilities.py @@ -36,7 +36,7 @@ class Responsibilities: def get(hard, author_name): author_blames = {} - for i in blame.get(hard).blames.items(): + for i in blame.get(hard, changes.get(hard)).blames.items(): if (author_name == i[0][0]): total_rows = i[1].rows - i[1].comments if total_rows > 0: @@ -58,7 +58,7 @@ class ResponsibilitiesOutput(Outputable): def output_text(self): 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).blames)): + for i in sorted(set(i[0] for i in blame.get(self.hard, self.changes).blames)): responsibilities = sorted(((i[1], i[0]) for i in Responsibilities.get(self.hard, i)), reverse=True) if responsibilities: print("\n" + i, _(MOSTLY_RESPONSIBLE_FOR_TEXT) + ":") @@ -77,13 +77,13 @@ class ResponsibilitiesOutput(Outputable): resp_xml = "<div><div class=\"box\" id=\"responsibilities\">" resp_xml += "<p>" + _(RESPONSIBILITIES_INFO_TEXT) + ".</p>" - for i in sorted(set(i[0] for i in blame.get(self.hard).blames)): + for i in sorted(set(i[0] for i in blame.get(self.hard, self.changes).blames)): responsibilities = sorted(((i[1], i[0]) for i in Responsibilities.get(self.hard, i)), reverse=True) if responsibilities: resp_xml += "<div>" if format.get_selected() == "html": - author_email = self.changes.get_author_email(i) + author_email = self.changes.get_latest_email_by_author(i) resp_xml += "<h3><img src=\"{0}\"/>{1} {2}</h3>".format(gravatar.get_url(author_email, size=32), i, _(MOSTLY_RESPONSIBLE_FOR_TEXT)) else: @@ -103,10 +103,10 @@ class ResponsibilitiesOutput(Outputable): message_xml = "\t\t<message>" + _(RESPONSIBILITIES_INFO_TEXT) + "</message>\n" resp_xml = "" - for i in sorted(set(i[0] for i in blame.get(self.hard).blames)): + for i in sorted(set(i[0] for i in blame.get(self.hard, self.changes).blames)): responsibilities = sorted(((i[1], i[0]) for i in Responsibilities.get(self.hard, i)), reverse=True) if responsibilities: - author_email = self.changes.get_author_email(i) + author_email = self.changes.get_latest_email_by_author(i) resp_xml += "\t\t\t<author>\n" resp_xml += "\t\t\t\t<name>" + i + "</name>\n" diff --git a/gitinspector/timeline.py b/gitinspector/timeline.py index 4a7d1ce..0b49bc4 100644 --- a/gitinspector/timeline.py +++ b/gitinspector/timeline.py @@ -70,7 +70,7 @@ class TimelineData: return self.total_changes_by_period[period] def get_authors(self): - return sorted(set([(i[0][0], self.changes.get_author_email(i[0][0])) for i in self.entries.items()])) + return sorted(set([(i[0][0], self.changes.get_latest_email_by_author(i[0][0])) for i in self.entries.items()])) def get_author_signs_in_period(self, author, period, multiplier): authorinfo = self.entries.get((author, period), None)