Compare commits

...

7 Commits

Author SHA1 Message Date
arzzen
376887a394 update manpage 2025-06-17 19:01:38 +02:00
arzzen
cebb5a8f48 Merge pull request #188 from git-quick-stats/task/issue-116
_MENU_THEME=none
2025-06-17 18:52:01 +02:00
arzzen
95136d3f92 add filter ignored authors 2025-06-16 14:40:49 +02:00
arzzen
79c735c814 fix backslash 2025-06-16 14:36:11 +02:00
arzzen
ec6a95d2ef cleanup 2025-06-16 14:33:03 +02:00
arzzen
b65b100cd8 fix tests 2025-06-16 14:29:24 +02:00
arzzen
dd7719c3b0 sorting contribution stats 2025-06-16 14:03:21 +02:00
4 changed files with 298 additions and 202 deletions

View File

@@ -204,12 +204,22 @@ You can set the variable `_GIT_BRANCH` to set the branch of the stats. Works wit
export _GIT_BRANCH="master"
```
### Ignore authors
You can set the variable `_GIT_IGNORE_AUTHORS` to filter out specific authors. It will affect the "All contributors", ""Suggested code reviewers" and "New contributors" options.
```bash
export _GIT_IGNORE_AUTHORS="(author@examle.com|username)"
```
### Sorting contribution stats
You can sort contribution stats by field `name`, `commits`, `insertions`, `deletions`, or `lines` (total lines changed) and order (`asc`, `desc`). e.g.: `commits-desc`
```bash
export _GIT_SORT_BY="name-asc"
```
### Color themes
You can change to the legacy color scheme by toggling the variable `_MENU_THEME` between `default` and `legacy`.

View File

@@ -76,6 +76,14 @@ else
_ignore_authors=""
fi
# Sort by field and order for contribution stats
_GIT_SORT_BY=${_GIT_SORT_BY:-name-asc}
# If the user has not set a sort order, default to name-asc
if [[ ! "$_GIT_SORT_BY" =~ ^(name|commits|insertions|deletions|lines)-(asc|desc)$ ]]; then
echo "Invalid sort option: $_GIT_SORT_BY. Defaulting to 'name-asc'."
_GIT_SORT_BY="name-asc"
fi
# Default menu theme
# Set the legacy theme by typing "export _MENU_THEME=legacy"
_theme="${_MENU_THEME:=default}"
@@ -118,12 +126,15 @@ function commitsCalendarByAuthor() {
else if (d==7) printf "Sun ";
for (m=1; m<=12; m++) {
c = count[d][m]+0;
if (c==0) out="...";
else if (c<=9) out="░░░";
else if (c<=19) out="▒▒▒";
else out="▓▓▓";
printf "%s%s", out, (m<12?" ":"\n");
}
if (c==0)
out="...";
else if (c<=9)
out="░░░";
else if (c<=19)
out="▒▒▒";
else
out="▓▓▓";
printf "%s%s", out, (m<12?" ":"\n"); }
}
printf "\nLegend: ... = 0 ░░░ = 19 ▒▒▒ = 1019 ▓▓▓ = 20+ commits\n";
}
@@ -270,7 +281,9 @@ ADDITIONAL USAGE
You can set _GIT_BRANCH to set the branch of the stats
ex: export _GIT_BRANCH=master
You can set _GIT_IGNORE_AUTHORS to filter out specific authors
ex: export _GIT_IGNORE_AUTHORS=\"(author1|author2)\""
ex: export _GIT_IGNORE_AUTHORS=\"(author1|author2)\"
You can sort contribution stats by field \"name\", \"commits\", \"insertions\", \"deletions\", or \"lines\" - total lines changed and order - \"asc\", \"desc\"
ex: export _GIT_SORT_BY=\"name-asc\""
}
################################################################################
@@ -362,7 +375,7 @@ filter_ignored_authors() {
################################################################################
# DESC: Shows detailed contribution stats per author by parsing every commit in
# the repo and outputting their contribution stats
# the repo and outputting their contribution stats.
# ARGS: $branch (optional): Users can specify an alternative branch instead of
# the current default one
# OUTS: None
@@ -394,82 +407,151 @@ function detailedGitStats() {
optionPicked "Contribution stats (by author) on the current branch:"
fi
local sort_by="${_GIT_SORT_BY:-name-asc}"
local sort_field
local sort_order
local sort_command
sort_field=$(echo "$sort_by" | awk -F- '{print $1}')
sort_order=$(echo "$sort_by" | awk -F- '{print $2}')
local sort_key
case "$sort_field" in
name) sort_key=1 ;;
commits) sort_key=2 ;;
insertions) sort_key=3 ;;
deletions) sort_key=4 ;;
lines) sort_key=5 ;;
*)
echo "Invalid sort field: $sort_field. Defaulting to 'name'."
sort_key=1
;;
esac
echo -e "Sorting by: $sort_field ($sort_order)\n"
local sort_flags="-t'|' -k${sort_key},${sort_key}"
if [[ "$sort_field" != "name" ]]; then
sort_flags="${sort_flags}n"
fi
if [[ "$sort_order" == "desc" ]]; then
sort_flags="${sort_flags}r"
fi
sort_command="sort ${sort_flags}"
# 1. git log -> awk (extract data) -> sort -> awk (format output with graphs)
git -c log.showSignature=false log ${_branch} --use-mailmap $_merges --numstat \
--pretty="format:commit %H%nAuthor: %aN <%aE>%nDate: %ad%n%n%w(0,4,4)%B%n" \
"$_since" "$_until" $_log_options $_pathspec | LC_ALL=C awk '
function printStats(author) {
printf "\t%s:\n", author
if(more["total"] > 0) {
printf "\t insertions: %d\t(%.0f%%)\n", more[author], \
(more[author] / more["total"] * 100)
} else {
printf "\t insertions: %d\t(%.0f%%)\n", 0, 0
}
if(less["total"] > 0) {
printf "\t deletions: %d\t(%.0f%%)\n", less[author], \
(less[author] / less["total"] * 100)
} else {
printf "\t deletions: %d\t(%.0f%%)\n", 0, 0
}
if(file["total"] > 0) {
printf "\t files: %d\t(%.0f%%)\n", file[author], \
(file[author] / file["total"] * 100)
}
if(commits["total"] > 0) {
printf "\t commits: %d\t(%.0f%%)\n", commits[author], \
(commits[author] / commits["total"] * 100)
}
if (first[author] != "") {
if ( ((more["total"] + less["total"]) * 100) > 0) {
printf "\t lines changed: %d\t", more[author] + less[author]
printf "(%.0f%%)\n", ((more[author] + less[author]) / \
(more["total"] + less["total"]) * 100)
}
else {
printf "\t lines changed: %d\t(0%%)\n", (more[author] + less[author])
}
printf "\t first commit: %s\n", first[author]
printf "\t last commit: %s\n", last[author]
}
printf "\n"
}
"$_since" "$_until" $_log_options $_pathspec |
LC_ALL=C awk '
# This first awk script extracts raw data into a delimited format
/^Author:/ {
$1 = ""
author = $0
commits[author] += 1
commits["total"] += 1
$1 = ""; author = $0;
commits[author] += 1;
}
/^Date:/ {
$1="";
first[author] = substr($0, 2)
if(last[author] == "" ) { last[author] = first[author] }
$1=""; current_date = substr($0, 2);
if (last[author] == "") { last[author] = current_date; }
first[author] = current_date;
}
/^[0-9]/ {
more[author] += $1
less[author] += $2
file[author] += 1
more["total"] += $1
less["total"] += $2
file["total"] += 1
more[author] += $1;
less[author] += $2;
file[author] += 1;
}
END {
for (author in commits) {
if (author != "total") {
printStats(author)
lines_changed = more[author] + less[author];
printf "%s|%d|%d|%d|%d|%d|%s|%s\n",
author, commits[author]+0, more[author]+0, less[author]+0,
lines_changed, file[author]+0, first[author], last[author];
}
}' |
eval "$sort_command" | filter_ignored_authors |
LC_ALL=C awk '
# This second awk script stores data, calculates totals, and then formats output with graphs
BEGIN {
FS = "|";
total_commits = 0; total_insertions = 0; total_deletions = 0;
total_lines = 0; total_files = 0;
num_authors = 0; # Counter for stored authors
}
printStats("total")
{
# Store all data for a second pass after totals are known
authors[num_authors] = $1;
commits_arr[num_authors] = $2;
insertions_arr[num_authors] = $3;
deletions_arr[num_authors] = $4;
lines_changed_arr[num_authors] = $5;
files_arr[num_authors] = $6;
first_commit_arr[num_authors] = $7;
last_commit_arr[num_authors] = $8;
# Accumulate overall totals
total_commits += $2;
total_insertions += $3;
total_deletions += $4;
total_lines += $5;
total_files += $6;
num_authors++;
}
END {
for (j = 0; j < num_authors; j++) {
author = authors[j];
current_commits = commits_arr[j];
current_insertions = insertions_arr[j];
current_deletions = deletions_arr[j];
current_lines_changed = lines_changed_arr[j];
current_files = files_arr[j];
current_first_commit = first_commit_arr[j];
current_last_commit = last_commit_arr[j];
printf "\t%s:\n", author;
# Commits graph
if (total_commits > 0) {
commit_percent = (current_commits * 100.0) / total_commits;
printf "\t commits: %d (%.1f%%)\n", current_commits, commit_percent;
} else {
printf "\t commits: %d\n", current_commits;
}
# Insertions graph
if (total_insertions > 0) {
insert_percent = (current_insertions * 100.0) / total_insertions;
printf "\t insertions: %d (%.1f%%)\n", current_insertions, insert_percent;
} else {
printf "\t insertions: %d\n", current_insertions;
}
# Deletions graph
if (total_deletions > 0) {
delete_percent = (current_deletions * 100.0) / total_deletions;
printf "\t deletions: %d (%.1f%%)\n", current_deletions, delete_percent;
} else {
printf "\t deletions: %d\n", current_deletions;
}
# Lines changed graph
if (total_lines > 0) {
lines_percent = (current_lines_changed * 100.0) / total_lines;
printf "\t lines changed: %d (%.1f%%)\n", current_lines_changed, lines_percent;
} else {
printf "\t lines changed: %d\n", current_lines_changed;
}
printf "\t files: %d\n", current_files;
printf "\t first commit: %s\n", current_first_commit;
printf "\t last commit: %s\n\n", current_last_commit;
}
# Print overall totals
printf "\t%s:\n", " total";
printf "\t commits: %d\n", total_commits;
printf "\t insertions: %d\n", total_insertions;
printf "\t deletions: %d\n", total_deletions;
printf "\t lines changed: %d\n", total_lines;
printf "\t files: %d\n\n", total_files;
}'
}
@@ -556,7 +638,8 @@ function csvOutput() {
# Check if requesting for a specific branch
if [[ -n "${branch}" ]]; then
# Check if branch exist
if [[ $(git show-ref refs/heads/"${branch}") ]] ; then
if [[ $(git show-ref refs/heads/"${branch}") ]] ;
then
is_branch_existing=true
_branch="${branch}"
else
@@ -891,7 +974,8 @@ function commitsByYear() {
echo -e "\tyear\tsum"
# Add time strings to make these a touch more robust
for year in $(seq "$startYear" "$endYear"); do
for year in $(seq "$startYear" "$endYear");
do
if [[ "$year" = "$startYear" ]]; then
__since=$_since
__until="--until=$year-12-31 23:59:59"
@@ -920,8 +1004,7 @@ function commitsByYear() {
for (year in count) {
s="|";
if (total > 0) {
percent = ((count[year] / total) * 100) / 1.25;
for (i = 1; i <= percent; ++i) {
percent = ((count[year] / total) * 100) / 1.25; for (i = 1; i <= percent; ++i) {
s=s"█"
}
printf( "\t%s\t%-0s\t%s\n", year, count[year], s );
@@ -953,8 +1036,7 @@ function commitsByMonth() {
for (month in count) {
s="|";
if (total > 0) {
percent = ((count[month] / total) * 100) / 1.25;
for (i = 1; i <= percent; ++i) {
percent = ((count[month] / total) * 100) / 1.25; for (i = 1; i <= percent; ++i) {
s=s"█"
}
printf( "\t%s\t%-0s\t%s\n", month, count[month], s );
@@ -986,20 +1068,17 @@ function commitsByWeekday() {
do
echo -en "\t$counter\t$i\t"
git -c log.showSignature=false shortlog -n $_merges --format='%ad %s' \
"${_author}" "$_since" "$_until" $_log_options |
grep -cE "^ * $i \w\w\w [0-9]{1,2} " || continue
"${_author}" "$_since" "$_until" $_log_options | grep -cE "^ * $i \w\w\w [0-9]{1,2} " || continue
counter=$((counter+1))
done | awk '{
}
NR == FNR {
count[$1" "$2] = $3;
total += $3;
count[$1" "$2] = $3; total += $3;
next
}
END{
for (day in count) {
s="|";
if (total > 0) {
s="|"; if (total > 0) {
percent = ((count[day] / total) * 100) / 1.25;
for (i = 1; i <= percent; ++i) {
s=s"█"
@@ -1034,8 +1113,7 @@ function commitsByHour() {
do
echo -ne "\t$i\t"
git -c log.showSignature=false shortlog -n $_merges --format='%ad %s' \
"${_author}" "$_since" "$_until" $_log_options |
grep -cE '[0-9] '$i':[0-9]' || continue
"${_author}" "$_since" "$_until" $_log_options | grep -cE '[0-9] '$i':[0-9]' || continue
done | awk '{
count[$1] = $2
total += $2
@@ -1044,8 +1122,7 @@ function commitsByHour() {
for (hour in count) {
s="|";
if (total > 0) {
percent = ((count[hour] / total) * 100) / 1.25;
for (i = 1; i <= percent; ++i) {
percent = ((count[hour] / total) * 100) / 1.25; for (i = 1; i <= percent; ++i) {
s=s"█"
}
printf( "\t%s\t%-0s\t%s\n", hour, count[hour], s );
@@ -1111,7 +1188,8 @@ if ! git rev-parse --is-inside-work-tree > /dev/null; then
exit 1
fi
# Parse non-interative commands
# Parse non-interactive commands
if [[ "$#" -eq 1 ]]; then
case "$1" in
# GENERATE OPTIONS
@@ -1207,17 +1285,19 @@ if [[ "$#" -eq 1 ]]; then
# SUGGEST OPTIONS
-r|--suggest-reviewers) suggestReviewers;;
-h|-\?|--help) usage;;
*) echo "Invalid argument"; usage; exit 1;;
*) echo "Invalid argument: $1"; usage; exit 1;;
esac
exit 0;
fi
[[ "$#" -gt 1 ]] && { echo "Invalid arguments"; usage; exit 1; }
# Parse interactive commands
clear
showMenu
# If no args, run interactive mode
if [[ "$#" -eq 0 ]]; then
# Parse interactive commands
clear
showMenu
while [[ "${opt}" != "" ]]; do
while [[ "${opt}" != "" ]]; do
clear
case "${opt}" in
1) detailedGitStats; showMenu;;
@@ -1261,7 +1341,6 @@ while [[ "${opt}" != "" ]]; do
11) newDate=""
while [[ -z "${newDate}" ]]; do
read -r -p "Since what date? (e.g. '2023-04-13', '13 April 2023', 'last Thursday') " newDate
# Test if the date provide is valid and try again if it isn't.
if ! date -d "${newDate}" +%s > /dev/null 2>&1; then
newDate=""
fi
@@ -1298,4 +1377,5 @@ while [[ "${opt}" != "" ]]; do
q|"\n") exit;;
*) clear; optionPicked "Pick an option from the menu"; showMenu;;
esac
done
done
fi

View File

@@ -165,7 +165,11 @@ You can also set _GIT_MERGE_VIEW to only show merge commits, example:
.PP
.B export _GIT_MERGE_VIEW="exclusive"
.PP
You can change to the legacy color scheme by toggling the variable `_MENU_THEME` between `default` and `legacy`. You can completely disable the color theme by setting the `_MENU_THEME` variable to `none`, example:
You can sort contribution stats by field "name", "commits", "insertions", "deletions", or "lines" (total lines changed) and order ("asc", "desc"). e.g.: "commits-desc"
.PP
.B export _GIT_SORT_BY="name-asc"
.PP
You can change to the legacy color scheme by toggling the variable "_MENU_THEME" between "default" and "legacy". You can completely disable the color theme by setting the "_MENU_THEME" variable to "none", example:
.PP
.B export _MENU_THEME=legacy
.PP

View File

@@ -11,7 +11,7 @@
. tests/assert.sh -v
src="./git-quick-stats"
assert "$src fail" "Invalid argument
assert "$src fail" "Invalid argument: fail
NAME
git-quick-stats - Simple and efficient way to access various stats in a git repo
@@ -98,14 +98,16 @@ ADDITIONAL USAGE
You can set _GIT_BRANCH to set the branch of the stats
ex: export _GIT_BRANCH=master
You can set _GIT_IGNORE_AUTHORS to filter out specific authors
ex: export _GIT_IGNORE_AUTHORS=\"(author1|author2)\""
ex: export _GIT_IGNORE_AUTHORS=\"(author1|author2)\"
You can sort contribution stats by field \"name\", \"commits\", \"insertions\", \"deletions\", or \"lines\" - total lines changed and order - \"asc\", \"desc\"
ex: export _GIT_SORT_BY=\"name-asc\""
assert_raises "$src fail" 1
assert_contains "$src --suggest-reviewers" "Suggested code reviewers (based on git history)"
assert_success "$src --suggest-reviewers"
assert_contains "$src --detailed-git-stats" "Contribution stats"
assert_contains "$src --detailed-git-stats" "Contribution stats (by author) on the current branch"
assert_success "$src --detailed-git-stats"
assert_contains "$src --commits-per-day" "Git commits per date"