Skip to content
Snippets Groups Projects
Commit 5ee7dcb9 authored by Christopher Bohn's avatar Christopher Bohn :thinking:
Browse files

Now shows contributions by file type

- Hand-edited changes (source code, markdown, etc) show *line* changes
- Binary files and auto-generated changes (html, fxml, etc) show *file*
  changes
parent 15adf237
No related branches found
No related tags found
No related merge requests found
...@@ -215,6 +215,12 @@ class GitlabCommit: ...@@ -215,6 +215,12 @@ class GitlabCommit:
def is_merge(self) -> bool: def is_merge(self) -> bool:
return len(self.gitlab_commit.parent_ids) > 1 return len(self.gitlab_commit.parent_ids) > 1
def get_id(self) -> str:
return self.gitlab_commit.id
def get_short_id(self) -> str:
return self.gitlab_commit.short_id
# noinspection PyShadowingNames # noinspection PyShadowingNames
def get_diffs(self) -> List[Dict[str, Union[str, int]]]: def get_diffs(self) -> List[Dict[str, Union[str, int]]]:
diffs: List[Dict[str, Union[str, int]]] = [] diffs: List[Dict[str, Union[str, int]]] = []
...@@ -234,6 +240,34 @@ class GitlabCommit: ...@@ -234,6 +240,34 @@ class GitlabCommit:
deletions += diff['-'] deletions += diff['-']
return max(insertions, deletions) return max(insertions, deletions)
# noinspection PyShadowingNames
def get_diff_size_by_filetype(self) -> Dict[str, int]:
binary_like_text_files = {"css", "csv", "fxml", "html"} # TODO soft-code this
# noinspection SpellCheckingInspection
binary_files = {"docx", "gif", "jpg", "jpeg", "pdf", "png", "pptx", "svg", "xlsx"} # TODO this, too
return_diff: Dict[str, int] = {}
insert_diff: Dict[str, int] = {}
delete_diff: Dict[str, int] = {}
if not self.is_merge():
for diff in self.get_diffs():
file = diff['file'].split('.')
if len(file) == 1:
filetype = 'no type'
else:
filetype = file[-1]
if filetype not in return_diff.keys():
return_diff[filetype] = 0
insert_diff[filetype] = 0
delete_diff[filetype] = 0
if filetype in binary_files.union(binary_like_text_files):
insert_diff[filetype] += 1
else:
insert_diff[filetype] += diff['+']
delete_diff[filetype] += diff['-']
for key in return_diff.keys():
return_diff[key] = max(insert_diff[key], delete_diff[key])
return return_diff
@staticmethod @staticmethod
def _number_of_lines_too_long(lines, subject_line_length, message_line_length): def _number_of_lines_too_long(lines, subject_line_length, message_line_length):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
......
import math
import textwrap import textwrap
from datetime import date from datetime import date
from math import ceil, log10 from math import log10, floor
from typing import Tuple from typing import Tuple
from gitlab import GitlabError from gitlab import GitlabError
...@@ -94,8 +93,24 @@ def get_project_prefix(canvas_groups): ...@@ -94,8 +93,24 @@ def get_project_prefix(canvas_groups):
return prefix return prefix
def _combine_typed_contributions(typed_sizes: Tuple[Dict[str, int]]) -> Dict[str, int]:
combined_typed_size: Dict[str, int] = {}
keys: Set[str] = set()
for typed_size in typed_sizes:
keys = keys.union(typed_size.keys())
for key in keys:
combined_typed_size[key] = 0
for typed_size in typed_sizes:
if key in typed_size.keys():
combined_typed_size[key] += typed_size[key]
return combined_typed_size
def display_git_contributions(project: GitlabProject): def display_git_contributions(project: GitlabProject):
# TODO: recognize that this only works for projects in namespace; will need to ask whether to retrieve project. # TODO: recognize that this only works for projects in namespace; will need to ask whether to retrieve project.
binary_like_text_files = {"css", "csv", "fxml", "html"} # TODO soft-code this
# noinspection SpellCheckingInspection
binary_files = {"docx", "gif", "jpg", "jpeg", "pdf", "png", "pptx", "svg", "xlsx"} # TODO this, too
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
project_commits: List[GitlabCommit] project_commits: List[GitlabCommit]
if assignment_start_date == '': if assignment_start_date == '':
...@@ -103,6 +118,7 @@ def display_git_contributions(project: GitlabProject): ...@@ -103,6 +118,7 @@ def display_git_contributions(project: GitlabProject):
else: else:
project_commits = project.get_commits(after_date=assignment_start_date) project_commits = project.get_commits(after_date=assignment_start_date)
contributions: Dict[str, int] = {} # TODO: also broaden to multiple branches? contributions: Dict[str, int] = {} # TODO: also broaden to multiple branches?
typed_contributions: Dict[str, Dict[str, int]] = {}
timestamps: Dict[str, List[datetime]] = {} timestamps: Dict[str, List[datetime]] = {}
contributors: Set[Tuple[str, str]] = set() contributors: Set[Tuple[str, str]] = set()
# noinspection PyShadowingNames # noinspection PyShadowingNames
...@@ -111,24 +127,33 @@ def display_git_contributions(project: GitlabProject): ...@@ -111,24 +127,33 @@ def display_git_contributions(project: GitlabProject):
author = commit.get_author() author = commit.get_author()
contributors.add((author['name'], author['email'])) contributors.add((author['name'], author['email']))
email = author['email'] # TODO: manage aliases email = author['email'] # TODO: manage aliases
size = commit.get_diff_size() # TODO: distinguish between file types size = commit.get_diff_size()
typed_size = commit.get_diff_size_by_filetype()
if email != 'bohn@unl.edu': # TODO: un-hard-code this -- may not be necessary with a starting date if email != 'bohn@unl.edu': # TODO: un-hard-code this -- may not be necessary with a starting date
if email not in contributions: if email not in contributions:
contributions[email] = 0 contributions[email] = 0
typed_contributions[email] = {}
timestamps[email] = [] timestamps[email] = []
contributions[email] += size contributions[email] += size
typed_sizes: Tuple[Dict[str, int]] = (typed_contributions[email], typed_size)
typed_contributions[email] = _combine_typed_contributions(typed_sizes)
timestamps[email].append(commit.get_timestamp()) timestamps[email].append(commit.get_timestamp())
print(f'Contributions by each partner to {project} :') print(f'Contributions by each partner to {project} :')
for contribution in contributions: for contribution in contributions:
contributor = list(filter(lambda c: c[1] == contribution, contributors))[0] contributor = list(filter(lambda c: c[1] == contribution, contributors))[0]
email = contributor[1] email = contributor[1]
typed_contribution = typed_contributions[email]
timestamps[email].sort() timestamps[email].sort()
number_of_commits = len(timestamps[email]) number_of_commits = len(timestamps[email])
print(f'\t{contributor}') print(f'\t{contributor}')
print( print(
f'\t{str(contributions[contribution]).rjust(5)} line changes in {str(number_of_commits).rjust(3)} commits') f'\t{str(contributions[contribution]).rjust(5)} total line changes in '
f'{str(number_of_commits).rjust(3)} commits')
for filetype in sorted(typed_contribution.keys()):
change_type = 'file' if filetype in binary_files.union(binary_like_text_files) else 'line'
print(f'\t\t\t{filetype} {str(typed_contribution[filetype]).rjust(10-len(filetype))} {change_type} changes')
print(f'\t\tFirst commit: {timestamps[email][0]}') print(f'\t\tFirst commit: {timestamps[email][0]}')
print(f'\t\tMedian commit: {timestamps[email][math.floor(number_of_commits/2)]}') print(f'\t\tMedian commit: {timestamps[email][floor(number_of_commits/2)]}')
print(f'\t\tLast commit: {timestamps[email][-1]}') print(f'\t\tLast commit: {timestamps[email][-1]}')
...@@ -171,8 +196,7 @@ if __name__ == '__main__': ...@@ -171,8 +196,7 @@ if __name__ == '__main__':
if option is options[1]: if option is options[1]:
print('Which group?') print('Which group?')
student_groups = [select_from_list(student_groups, 'student group')] student_groups = [select_from_list(student_groups, 'student group')]
zero_padding: int = ceil(log10(len(projects))) # remove this after 24pair is graded zero_padding: int = floor(log10(len(projects))) + 1
# zero_padding: int = floor(log10(len(projects))) + 1 # leaving this here until 24pair is graded
assignment_start_date = get_assignment_start() # TODO: only need this if grading git histories assignment_start_date = get_assignment_start() # TODO: only need this if grading git histories
for student_group in student_groups: # TODO: Skip past graded groups for student_group in student_groups: # TODO: Skip past graded groups
input(f'\n\nPress Enter to grade {student_group}') input(f'\n\nPress Enter to grade {student_group}')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment