Compare commits

...

22 Commits

Author SHA1 Message Date
arzzen
125eec7136 Merge pull request #195 from tomice/issues/194-flatten-awk-arrays-for-bsd-awk-compat
Flatten arrays for wider awk compatibility
2025-09-02 11:46:39 +02:00
Tom Ice
d61c812247 Flatten arrays for wider awk compatibility
* The current implementation of the calendar heatmap by author
  contains multidimensional arrays. This works with newer versions
  of awk, but can cause issues with systems that use an older variant
  which cannot easily handle these calculations. macOS is especially
  impacted by this.

  By flattening the arrays from a multidimensional array to a single
  dimensional array, we can maintain greater compatibility across awk
  variants without requiring the user to install additional versions
  of awk.

Addresses issue #194
2025-08-31 16:22:59 -04:00
arzzen
261262ab4e Update Dockerfile - add coreutils 2025-08-03 18:48:37 +02:00
arzzen
e968ef62b9 Merge pull request #190 from git-quick-stats/task/commits-heatmap
commits heatmap
2025-06-29 08:57:05 +02:00
arzzen
74261c1e50 smallfixes 2025-06-29 08:55:49 +02:00
arzzen
4eade09673 Merge branch 'task/commits-heatmap' of github.com:git-quick-stats/git-quick-stats into task/commits-heatmap 2025-06-28 22:25:09 +02:00
arzzen
47a9151886 fix: tput colors, local vars 2025-06-28 22:24:51 +02:00
arzzen
916d5e1e0f Merge branch 'master' of github.com:git-quick-stats/git-quick-stats into task/commits-heatmap 2025-06-25 17:50:40 +02:00
arzzen
17a9d1427b Update git-quick-stats 2025-06-22 20:06:31 +02:00
arzzen
091562d4f8 Merge pull request #191 from git-quick-stats/task/citation-file
citation file
2025-06-22 20:03:57 +02:00
arzzen
748576b541 citation file 2025-06-22 20:03:38 +02:00
arzzen
f43ab22172 remove sponsors 2025-06-22 19:51:06 +02:00
arzzen
4d2e52aa6d commits heatmap 2025-06-22 19:49:49 +02:00
arzzen
08c0ca1d85 Update README.md 2025-06-17 19:24:24 +02:00
arzzen
e8843da10e Merge pull request #189 from git-quick-stats/134-feature-request-sorting-contribution-stats
Sorting contribution stats
2025-06-17 19:03:55 +02:00
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
6 changed files with 1075 additions and 840 deletions

25
CITATION.cff Normal file
View File

@@ -0,0 +1,25 @@
cff-version: 1.2.0
title: Git Quick Stats
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- name-particle: tomice
- name-particle: arzzen
identifiers:
- type: url
value: "https://github.com/git-quick-stats"
description: >-
Simple and efficient way to access various statistics
in a git repository
repository-code: "https://github.com/git-quick-stats/git-quick-stats"
url: "https://git-quick-stats.sh/"
abstract: >-
Git-quick-stats is a simple and efficient way to access
various statistics in a git repository.
keywords:
- git
- stats
license: MIT
version: 2.6.2

View File

@@ -4,7 +4,7 @@ FROM alpine
COPY . /app COPY . /app
# Install required packages & build git-quick-stats # Install required packages & build git-quick-stats
RUN apk add --no-cache bash git make ncurses util-linux \ RUN apk add --no-cache bash git make ncurses coreutils util-linux \
&& cd /app \ && cd /app \
&& make install \ && make install \
&& rm -rf /app \ && rm -rf /app \

View File

@@ -6,7 +6,7 @@
> >
> Any git repository may contain tons of information about commits, contributors, and files. Extracting this information is not always trivial, mostly because there are a gadzillion options to a gadzillion git commands - I don't think there is a single person alive who knows them all. Probably not even [Linus Torvalds](https://github.com/torvalds) himself :). > Any git repository may contain tons of information about commits, contributors, and files. Extracting this information is not always trivial, mostly because there are a gadzillion options to a gadzillion git commands - I don't think there is a single person alive who knows them all. Probably not even [Linus Torvalds](https://github.com/torvalds) himself :).
![mainMenuScreenshot](https://github.com/user-attachments/assets/05a20ff1-44f6-4e44-9d62-c2c089f9ff4e) ![mainImageScreenshot](https://github.com/user-attachments/assets/7d8637a4-5a67-49f6-8724-ca7548b987c6)
## Table of Contents ## Table of Contents
@@ -54,9 +54,11 @@
## Screenshots ## Screenshots
![commitsByWeekdayScreenshot](https://github.com/user-attachments/assets/3a55f3ac-8801-4bbf-9b3a-92b53a64631e) ![commitsByWeekdayScreenshot](https://github.com/user-attachments/assets/3496d245-6385-47d1-878a-726e79100eb1)
![commitsByHourScreenshot](https://github.com/user-attachments/assets/d7de5280-8bb9-4391-9c6c-7e688f2df171) ![commitsByHourScreenshot](https://github.com/user-attachments/assets/9f1d69d9-46e0-411d-a5ed-905ffdfb887a)
![commitActivityScreenshot](https://github.com/user-attachments/assets/693fff31-65c7-4b9f-a011-6114a2d10a26)
## Usage ## Usage
@@ -122,10 +124,10 @@ LIST OPTIONS
displays a list of commits per author displays a list of commits per author
-d, --commits-per-day -d, --commits-per-day
displays a list of commits per day displays a list of commits per day
-Y, --commits-by-year
displays a list of commits per year
-m, --commits-by-month -m, --commits-by-month
displays a list of commits per month displays a list of commits per month
-Y, --commits-by-year
displays a list of commits per year
-w, --commits-by-weekday -w, --commits-by-weekday
displays a list of commits per weekday displays a list of commits per weekday
-W, --commits-by-author-by-weekday -W, --commits-by-author-by-weekday
@@ -139,6 +141,12 @@ LIST OPTIONS
-Z, --commits-by-author-by-timezone -Z, --commits-by-author-by-timezone
displays a list of commits per timezone by author displays a list of commits per timezone by author
CALENDAR OPTIONS
-k, --commits-calendar-by-author
shows a calendar heatmap of commits per day-of-week per month for a given author
-H, --commits-heatmap
shows a heatmap of commits per day-of-week per month for the last 30 days
SUGGEST OPTIONS SUGGEST OPTIONS
-r, --suggest-reviewers -r, --suggest-reviewers
show the best people to contact to review code show the best people to contact to review code
@@ -204,12 +212,30 @@ You can set the variable `_GIT_BRANCH` to set the branch of the stats. Works wit
export _GIT_BRANCH="master" 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. 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 ```bash
export _GIT_IGNORE_AUTHORS="(author@examle.com|username)" 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"
```
### Commit days
You can set \_GIT_DAYS to set the number of days for the heatmap
```bash
export _GIT_DAYS=30
```
### Color themes ### Color themes
You can change to the legacy color scheme by toggling the variable `_MENU_THEME` between `default` and `legacy`. You can change to the legacy color scheme by toggling the variable `_MENU_THEME` between `default` and `legacy`.
@@ -383,24 +409,3 @@ MIT see [LICENSE][] for the full license text.
This project exists thanks to all the people who contribute. This project exists thanks to all the people who contribute.
[![contributors](https://opencollective.com/git-quick-stats/contributors.svg?width=890&button=false)](https://github.com/git-quick-stats/git-quick-stats/graphs/contributors) [![contributors](https://opencollective.com/git-quick-stats/contributors.svg?width=890&button=false)](https://github.com/git-quick-stats/git-quick-stats/graphs/contributors)
### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/git-quick-stats#backer)]
[![backers](https://opencollective.com/git-quick-stats/backers.svg?width=890)](https://opencollective.com/git-quick-stats#backers)
### Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/git-quick-stats#sponsor)]
[![sponsor0](https://opencollective.com/git-quick-stats/sponsor/0/avatar.svg?v=1)](https://opencollective.com/git-quick-stats/sponsor/0/website)
[![sponsor1](https://opencollective.com/git-quick-stats/sponsor/1/avatar.svg)](https://opencollective.com/git-quick-stats/sponsor/1/website)
[![sponsor2](https://opencollective.com/git-quick-stats/sponsor/2/avatar.svg)](https://opencollective.com/git-quick-stats/sponsor/2/website)
[![sponsor3](https://opencollective.com/git-quick-stats/sponsor/3/avatar.svg)](https://opencollective.com/git-quick-stats/sponsor/3/website)
[![sponsor4](https://opencollective.com/git-quick-stats/sponsor/4/avatar.svg)](https://opencollective.com/git-quick-stats/sponsor/4/website)
[![sponsor5](https://opencollective.com/git-quick-stats/sponsor/5/avatar.svg)](https://opencollective.com/git-quick-stats/sponsor/5/website)
[![sponsor6](https://opencollective.com/git-quick-stats/sponsor/6/avatar.svg)](https://opencollective.com/git-quick-stats/sponsor/6/website)
[![sponsor7](https://opencollective.com/git-quick-stats/sponsor/7/avatar.svg)](https://opencollective.com/git-quick-stats/sponsor/7/website)
[![sponsor8](https://opencollective.com/git-quick-stats/sponsor/8/avatar.svg)](https://opencollective.com/git-quick-stats/sponsor/8/website)
[![sponsor9](https://opencollective.com/git-quick-stats/sponsor/9/avatar.svg)](https://opencollective.com/git-quick-stats/sponsor/9/website)

View File

@@ -10,6 +10,24 @@
set -o nounset set -o nounset
set -o errexit set -o errexit
# Global color variables using tput setaf
COLOR_NORMAL=$(tput sgr0)
COLOR_CYANL=$(tput setaf 6)
COLOR_BOLD=$(tput bold)
COLOR_RED=$(tput setaf 1)
COLOR_YELLOW=$(tput setaf 3)
COLOR_WHITE=$(tput setaf 7)
COLOR_BRIGHT_YELLOW=$(tput setaf 226)
COLOR_GOLD=$(tput setaf 220)
COLOR_ORANGE=$(tput setaf 214)
COLOR_DARK_ORANGE=$(tput setaf 208)
COLOR_RED_ORANGE=$(tput setaf 202)
COLOR_DARK_RED=$(tput setaf 160)
COLOR_DEEP_RED=$(tput setaf 88)
COLOR_DARKEST_RED=$(tput setaf 52)
COLOR_GRAY=$(tput setaf 240)
COLOR_RESET=$(tput sgr0)
# Beginning git log date. Respects all git datetime formats # Beginning git log date. Respects all git datetime formats
# If $_GIT_SINCE is never set, look at the repository to find the first date. # If $_GIT_SINCE is never set, look at the repository to find the first date.
# NOTE: previously this put the date at the fixed GIT epoch (May 2005) # NOTE: previously this put the date at the fixed GIT epoch (May 2005)
@@ -76,6 +94,22 @@ else
_ignore_authors="" _ignore_authors=""
fi 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
# Number of days to display in the heatmap
_commit_days=${_GIT_DAYS:-30}
# If the user has not set a number of days, default to 30
if ! [[ "$_commit_days" =~ ^[0-9]+$ ]] || (( _commit_days <= 0 )); then
echo "Invalid number of days: $_commit_days. Defaulting to 30."
_commit_days=30
fi
# Default menu theme # Default menu theme
# Set the legacy theme by typing "export _MENU_THEME=legacy" # Set the legacy theme by typing "export _MENU_THEME=legacy"
_theme="${_MENU_THEME:=default}" _theme="${_MENU_THEME:=default}"
@@ -104,7 +138,7 @@ function commitsCalendarByAuthor() {
cmd | getline weekday; cmd | getline weekday;
close(cmd); close(cmd);
# weekday: 1=Mon, ..., 7=Sun # weekday: 1=Mon, ..., 7=Sun
count[weekday][mon]++; count[weekday * 12 + mon]++;
} }
END { END {
# Output matrix # Output matrix
@@ -117,19 +151,109 @@ function commitsCalendarByAuthor() {
else if (d==6) printf "Sat "; else if (d==6) printf "Sat ";
else if (d==7) printf "Sun "; else if (d==7) printf "Sun ";
for (m=1; m<=12; m++) { for (m=1; m<=12; m++) {
c = count[d][m]+0; c = count[d * 12 + m]+0;
if (c==0) out="..."; if (c==0)
else if (c<=9) out="░░░"; out="...";
else if (c<=19) out="▒▒▒"; else if (c<=9)
else out="▓▓▓"; out="░░░";
printf "%s%s", out, (m<12?" ":"\n"); else if (c<=19)
} out="▒▒▒";
else
out="▓▓▓";
printf "%s%s", out, (m<12?" ":"\n"); }
} }
printf "\nLegend: ... = 0 ░░░ = 19 ▒▒▒ = 1019 ▓▓▓ = 20+ commits\n"; printf "\nLegend: ... = 0 ░░░ = 19 ▒▒▒ = 1019 ▓▓▓ = 20+ commits\n";
} }
' '
} }
# DESC: Shows a heatmap of commits per hour of each day for the last 30 days
function commitsHeatmap() {
optionPicked "Commit Heatmap for the last $_commit_days days"
color_for_count() {
local n=$1
if (( n == 1 )); then
echo -n "${COLOR_BRIGHT_YELLOW}"
elif (( n < 2 )); then
echo -n "${COLOR_GOLD}"
elif (( n < 3 )); then
echo -n "${COLOR_ORANGE}"
elif (( n < 4 )); then
echo -n "${COLOR_DARK_ORANGE}"
elif (( n < 5 )); then
echo -n "${COLOR_RED_ORANGE}"
elif (( n < 6 )); then
echo -n "${COLOR_RED}"
elif (( n < 8 )); then
echo -n "${COLOR_DARK_RED}"
elif (( n < 10 )); then
echo -n "${COLOR_DEEP_RED}"
else
echo -n "${COLOR_DARKEST_RED}"
fi
}
printf "Day | Date/Hours |"
local h
for h in {0..23}; do
printf " %2d" "$h"
done
echo
echo "------------------------------------------------------------------------------------------"
local i
for i in $(seq $((_commit_days-1)) -1 0); do
local day=$(date -d "-$i days" +"%Y-%m-%d")
if [[ $(date -d "$day" +%u) -gt 5 ]]; then
echo -en "${COLOR_GRAY}"
else
echo -en "${COLOR_RESET}"
fi
local dayName=$(date -d "$day" +%a)
printf "%s | %s |" "$dayName" "$day"
declare -a commits_per_hour
local h
for h in {0..23}; do
commits_per_hour[$h]=0
done
IFS=$'\n' commits_per_hour=($(
git log --since="$day 00:00" --until="$day 23:59" --pretty=format:"%ci" 2>/dev/null |
awk '{split($2, t, ":"); h = t[1]+0; c[h]++} END {for(i=0;i<24;i++) print c[i]+0}'
))
unset IFS
local h
for h in {0..23}; do
local count=${commits_per_hour[$h]}
local color=$(color_for_count "$count")
if (( count == 0 )); then
echo -en " ${COLOR_GRAY}.${COLOR_RESET} "
else
echo -en "${color} █ ${COLOR_RESET}"
fi
done
echo
done
echo "------------------------------------------------------------------------------------------"
echo -e "\nLegend:"
echo -e " ${COLOR_BRIGHT_YELLOW}█${COLOR_RESET} 1 commit"
echo -e " ${COLOR_GOLD}█${COLOR_RESET} 2 commits"
echo -e " ${COLOR_ORANGE}█${COLOR_RESET} 3 commits"
echo -e " ${COLOR_DARK_ORANGE}█${COLOR_RESET} 4 commits"
echo -e " ${COLOR_RED_ORANGE}█${COLOR_RESET} 5 commits"
echo -e " ${COLOR_RED}█${COLOR_RESET} 6 commits"
echo -e " ${COLOR_DARK_RED}█${COLOR_RESET} 78 commits"
echo -e " ${COLOR_DEEP_RED}█${COLOR_RESET} 910 commits"
echo -e " ${COLOR_DARKEST_RED}█${COLOR_RESET} 11+ commits"
echo -e " ${COLOR_GRAY}.${COLOR_RESET} = no commits"
echo
}
################################################################################ ################################################################################
# HELPER AND MENU FUNCTIONS # HELPER AND MENU FUNCTIONS
@@ -245,6 +369,12 @@ LIST OPTIONS
-Z, --commits-by-author-by-timezone -Z, --commits-by-author-by-timezone
displays a list of commits per timezone by author displays a list of commits per timezone by author
CALENDAR OPTIONS
-k, --commits-calendar-by-author
shows a calendar heatmap of commits per day-of-week per month for a given author
-H, --commits-heatmap
shows a heatmap of commits per day-of-week per month for the last 30 days
SUGGEST OPTIONS SUGGEST OPTIONS
-r, --suggest-reviewers -r, --suggest-reviewers
show the best people to contact to review code show the best people to contact to review code
@@ -270,7 +400,11 @@ ADDITIONAL USAGE
You can set _GIT_BRANCH to set the branch of the stats You can set _GIT_BRANCH to set the branch of the stats
ex: export _GIT_BRANCH=master ex: export _GIT_BRANCH=master
You can set _GIT_IGNORE_AUTHORS to filter out specific authors 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\"
You can set _GIT_DAYS to set the number of days for the heatmap
ex: export _GIT_DAYS=30"
} }
################################################################################ ################################################################################
@@ -280,12 +414,6 @@ ADDITIONAL USAGE
################################################################################ ################################################################################
function showMenu() { function showMenu() {
# These are "global" and can be overriden from users if so desired # These are "global" and can be overriden from users if so desired
NORMAL=$(tput sgr0)
CYAN=$(tput setaf 6)
BOLD=$(tput bold)
RED=$(tput setaf 1)
YELLOW=$(tput setaf 3)
WHITE=$(tput setaf 7)
TITLES="" TITLES=""
TEXT="" TEXT=""
NUMS="" NUMS=""
@@ -295,29 +423,29 @@ function showMenu() {
# Adjustable color menu option # Adjustable color menu option
case "${_theme}" in case "${_theme}" in
"legacy" ) "legacy" )
TITLES="${BOLD}${RED}" TITLES="${COLOR_BOLD}${COLOR_RED}"
TEXT="${NORMAL}${CYAN}" TEXT="${COLOR_NORMAL}${COLOR_CYANL}"
NUMS="${BOLD}${YELLOW}" NUMS="${COLOR_BOLD}${COLOR_YELLOW}"
HELP_TXT="${NORMAL}${YELLOW}" HELP_TXT="${COLOR_NORMAL}${COLOR_YELLOW}"
EXIT_TXT="${BOLD}${RED}" EXIT_TXT="${COLOR_BOLD}${COLOR_RED}"
;; ;;
"none" ) "none" )
TITLES="${BOLD}" TITLES="${COLOR_BOLD}"
TEXT="${NORMAL}" TEXT="${COLOR_NORMAL}"
NUMS="${BOLD}" NUMS="${COLOR_BOLD}"
HELP_TXT="${NORMAL}" HELP_TXT="${COLOR_NORMAL}"
EXIT_TXT="${BOLD}" EXIT_TXT="${COLOR_BOLD}"
;; ;;
*) *)
TITLES="${BOLD}${CYAN}" TITLES="${COLOR_BOLD}${COLOR_CYANL}"
TEXT="${NORMAL}${WHITE}" TEXT="${COLOR_NORMAL}${COLOR_WHITE}"
NUMS="${NORMAL}${BOLD}${WHITE}" NUMS="${COLOR_NORMAL}${COLOR_BOLD}${COLOR_WHITE}"
HELP_TXT="${NORMAL}${CYAN}" HELP_TXT="${COLOR_NORMAL}${COLOR_CYANL}"
EXIT_TXT="${BOLD}${CYAN}" EXIT_TXT="${COLOR_BOLD}${COLOR_CYANL}"
;; ;;
esac esac
printf %b "\\n${TITLES} Generate:${NORMAL}\\n" printf %b "\\n${TITLES} Generate:${COLOR_NORMAL}\\n"
printf %b "${NUMS} 1)${TEXT} Contribution stats (by author)\\n" printf %b "${NUMS} 1)${TEXT} Contribution stats (by author)\\n"
printf %b "${NUMS} 2)${TEXT} Contribution stats (by author) on a specific branch\\n" printf %b "${NUMS} 2)${TEXT} Contribution stats (by author) on a specific branch\\n"
printf %b "${NUMS} 3)${TEXT} Git changelogs (last $_limit days)\\n" printf %b "${NUMS} 3)${TEXT} Git changelogs (last $_limit days)\\n"
@@ -344,8 +472,9 @@ function showMenu() {
printf %b "${NUMS} 22)${TEXT} Code reviewers (based on git history)\\n" printf %b "${NUMS} 22)${TEXT} Code reviewers (based on git history)\\n"
printf %b "\\n${TITLES} Calendar:\\n" printf %b "\\n${TITLES} Calendar:\\n"
printf %b "${NUMS} 23)${TEXT} Activity calendar by author\\n" printf %b "${NUMS} 23)${TEXT} Activity calendar by author\\n"
printf %b "${NUMS} 24)${TEXT} Activity heatmap for the last $_commit_days days\\n"
printf %b "\\n${HELP_TXT}Please enter a menu option or ${EXIT_TXT}press Enter to exit.\\n" printf %b "\\n${HELP_TXT}Please enter a menu option or ${EXIT_TXT}press Enter to exit.\\n"
printf %b "${TEXT}> ${NORMAL}" printf %b "${TEXT}> ${COLOR_NORMAL}"
read -r opt read -r opt
} }
@@ -362,7 +491,7 @@ filter_ignored_authors() {
################################################################################ ################################################################################
# DESC: Shows detailed contribution stats per author by parsing every commit in # 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 # ARGS: $branch (optional): Users can specify an alternative branch instead of
# the current default one # the current default one
# OUTS: None # OUTS: None
@@ -394,82 +523,151 @@ function detailedGitStats() {
optionPicked "Contribution stats (by author) on the current branch:" optionPicked "Contribution stats (by author) on the current branch:"
fi 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 \ 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" \ --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 ' "$_since" "$_until" $_log_options $_pathspec |
function printStats(author) { LC_ALL=C awk '
printf "\t%s:\n", author # This first awk script extracts raw data into a delimited format
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"
}
/^Author:/ { /^Author:/ {
$1 = "" $1 = ""; author = $0;
author = $0 commits[author] += 1;
commits[author] += 1
commits["total"] += 1
} }
/^Date:/ { /^Date:/ {
$1=""; $1=""; current_date = substr($0, 2);
first[author] = substr($0, 2) if (last[author] == "") { last[author] = current_date; }
if(last[author] == "" ) { last[author] = first[author] } first[author] = current_date;
} }
/^[0-9]/ { /^[0-9]/ {
more[author] += $1 more[author] += $1;
less[author] += $2 less[author] += $2;
file[author] += 1;
file[author] += 1
more["total"] += $1
less["total"] += $2
file["total"] += 1
} }
END { END {
for (author in commits) { for (author in commits) {
if (author != "total") { lines_changed = more[author] + less[author];
printStats(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 +754,8 @@ function csvOutput() {
# Check if requesting for a specific branch # Check if requesting for a specific branch
if [[ -n "${branch}" ]]; then if [[ -n "${branch}" ]]; then
# Check if branch exist # Check if branch exist
if [[ $(git show-ref refs/heads/"${branch}") ]] ; then if [[ $(git show-ref refs/heads/"${branch}") ]] ;
then
is_branch_existing=true is_branch_existing=true
_branch="${branch}" _branch="${branch}"
else else
@@ -891,7 +1090,8 @@ function commitsByYear() {
echo -e "\tyear\tsum" echo -e "\tyear\tsum"
# Add time strings to make these a touch more robust # 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 if [[ "$year" = "$startYear" ]]; then
__since=$_since __since=$_since
__until="--until=$year-12-31 23:59:59" __until="--until=$year-12-31 23:59:59"
@@ -920,8 +1120,7 @@ function commitsByYear() {
for (year in count) { for (year in count) {
s="|"; s="|";
if (total > 0) { if (total > 0) {
percent = ((count[year] / total) * 100) / 1.25; percent = ((count[year] / total) * 100) / 1.25; for (i = 1; i <= percent; ++i) {
for (i = 1; i <= percent; ++i) {
s=s"█" s=s"█"
} }
printf( "\t%s\t%-0s\t%s\n", year, count[year], s ); printf( "\t%s\t%-0s\t%s\n", year, count[year], s );
@@ -953,8 +1152,7 @@ function commitsByMonth() {
for (month in count) { for (month in count) {
s="|"; s="|";
if (total > 0) { if (total > 0) {
percent = ((count[month] / total) * 100) / 1.25; percent = ((count[month] / total) * 100) / 1.25; for (i = 1; i <= percent; ++i) {
for (i = 1; i <= percent; ++i) {
s=s"█" s=s"█"
} }
printf( "\t%s\t%-0s\t%s\n", month, count[month], s ); printf( "\t%s\t%-0s\t%s\n", month, count[month], s );
@@ -986,20 +1184,17 @@ function commitsByWeekday() {
do do
echo -en "\t$counter\t$i\t" echo -en "\t$counter\t$i\t"
git -c log.showSignature=false shortlog -n $_merges --format='%ad %s' \ git -c log.showSignature=false shortlog -n $_merges --format='%ad %s' \
"${_author}" "$_since" "$_until" $_log_options | "${_author}" "$_since" "$_until" $_log_options | grep -cE "^ * $i \w\w\w [0-9]{1,2} " || continue
grep -cE "^ * $i \w\w\w [0-9]{1,2} " || continue
counter=$((counter+1)) counter=$((counter+1))
done | awk '{ done | awk '{
} }
NR == FNR { NR == FNR {
count[$1" "$2] = $3; count[$1" "$2] = $3; total += $3;
total += $3;
next next
} }
END{ END{
for (day in count) { for (day in count) {
s="|"; s="|"; if (total > 0) {
if (total > 0) {
percent = ((count[day] / total) * 100) / 1.25; percent = ((count[day] / total) * 100) / 1.25;
for (i = 1; i <= percent; ++i) { for (i = 1; i <= percent; ++i) {
s=s"█" s=s"█"
@@ -1034,8 +1229,7 @@ function commitsByHour() {
do do
echo -ne "\t$i\t" echo -ne "\t$i\t"
git -c log.showSignature=false shortlog -n $_merges --format='%ad %s' \ git -c log.showSignature=false shortlog -n $_merges --format='%ad %s' \
"${_author}" "$_since" "$_until" $_log_options | "${_author}" "$_since" "$_until" $_log_options | grep -cE '[0-9] '$i':[0-9]' || continue
grep -cE '[0-9] '$i':[0-9]' || continue
done | awk '{ done | awk '{
count[$1] = $2 count[$1] = $2
total += $2 total += $2
@@ -1044,8 +1238,7 @@ function commitsByHour() {
for (hour in count) { for (hour in count) {
s="|"; s="|";
if (total > 0) { if (total > 0) {
percent = ((count[hour] / total) * 100) / 1.25; percent = ((count[hour] / total) * 100) / 1.25; for (i = 1; i <= percent; ++i) {
for (i = 1; i <= percent; ++i) {
s=s"█" s=s"█"
} }
printf( "\t%s\t%-0s\t%s\n", hour, count[hour], s ); printf( "\t%s\t%-0s\t%s\n", hour, count[hour], s );
@@ -1111,7 +1304,8 @@ if ! git rev-parse --is-inside-work-tree > /dev/null; then
exit 1 exit 1
fi fi
# Parse non-interative commands
# Parse non-interactive commands
if [[ "$#" -eq 1 ]]; then if [[ "$#" -eq 1 ]]; then
case "$1" in case "$1" in
# GENERATE OPTIONS # GENERATE OPTIONS
@@ -1197,27 +1391,30 @@ if [[ "$#" -eq 1 ]]; then
read -r -p "Which author? " author read -r -p "Which author? " author
done done
commitsByTimezone "${author}";; commitsByTimezone "${author}";;
# ACTIVITY OPTIONS # CALENDAR OPTIONS
-k|--commits-calendar-by-author) -k|--commits-calendar-by-author)
author="${_GIT_AUTHOR:-}" author="${_GIT_AUTHOR:-}"
while [[ -z "${author}" ]]; do while [[ -z "${author}" ]]; do
read -r -p "Which author? " author read -r -p "Which author? " author
done done
commitsCalendarByAuthor "${author}";; commitsCalendarByAuthor "${author}";;
-H|--commits-heatmap) commitsHeatmap;;
# SUGGEST OPTIONS # SUGGEST OPTIONS
-r|--suggest-reviewers) suggestReviewers;; -r|--suggest-reviewers) suggestReviewers;;
-h|-\?|--help) usage;; -h|-\?|--help) usage;;
*) echo "Invalid argument"; usage; exit 1;; *) echo "Invalid argument: $1"; usage; exit 1;;
esac esac
exit 0; exit 0;
fi fi
[[ "$#" -gt 1 ]] && { echo "Invalid arguments"; usage; exit 1; } [[ "$#" -gt 1 ]] && { echo "Invalid arguments"; usage; exit 1; }
# Parse interactive commands # If no args, run interactive mode
clear if [[ "$#" -eq 0 ]]; then
showMenu # Parse interactive commands
clear
showMenu
while [[ "${opt}" != "" ]]; do while [[ "${opt}" != "" ]]; do
clear clear
case "${opt}" in case "${opt}" in
1) detailedGitStats; showMenu;; 1) detailedGitStats; showMenu;;
@@ -1261,7 +1458,6 @@ while [[ "${opt}" != "" ]]; do
11) newDate="" 11) newDate=""
while [[ -z "${newDate}" ]]; do while [[ -z "${newDate}" ]]; do
read -r -p "Since what date? (e.g. '2023-04-13', '13 April 2023', 'last Thursday') " newDate 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 if ! date -d "${newDate}" +%s > /dev/null 2>&1; then
newDate="" newDate=""
fi fi
@@ -1295,7 +1491,9 @@ while [[ "${opt}" != "" ]]; do
read -r -p "Which author? " author read -r -p "Which author? " author
done done
commitsCalendarByAuthor "${author}"; showMenu;; commitsCalendarByAuthor "${author}"; showMenu;;
24) commitsHeatmap; showMenu;;
q|"\n") exit;; q|"\n") exit;;
*) clear; optionPicked "Pick an option from the menu"; showMenu;; *) clear; optionPicked "Pick an option from the menu"; showMenu;;
esac esac
done done
fi

View File

@@ -19,6 +19,10 @@ This program allows you to see detailed information about a git repository.
.PP .PP
.SH GENERATE OPTIONS .SH GENERATE OPTIONS
.PP .PP
\fB\-h\fR, \-?, \fB\-\-help\fR
.IP
display this help text in the terminal
.PP
\fB\-T\fR, \fB\-\-detailed\-git\-stats\fR \fB\-T\fR, \fB\-\-detailed\-git\-stats\fR
.IP .IP
give a detailed list of git stats give a detailed list of git stats
@@ -136,9 +140,12 @@ displays a calendar-style grid of commit activity per day-of-week and month for
show the best people to contact to review code show the best people to contact to review code
.HP .HP
.PP .PP
\fB\-h\fR, \-?, \fB\-\-help\fR .SH CALENDAR OPTIONS
.IP .PP
display this help text in the terminal \fB\-k\fR, \fB\-\-commits\-calendar\-by\-author\fR outputs a visual grid of commit activity for a selected author, grouped by day-of-week (rows: Mon..Sun) and month (columns: Jan..Dec). Each cell is 3 characters wide, separated by one space.
.PP
.PP
\fB\-H\fR, \fB\-\-commits\-heatmap\fR shows a heatmap of commits per day per hour for the last 30 days
.PP .PP
.SH ADDITIONAL USAGE .SH ADDITIONAL USAGE
You can set _GIT_SINCE and _GIT_UNTIL to limit the git time log, example: You can set _GIT_SINCE and _GIT_UNTIL to limit the git time log, example:
@@ -165,7 +172,11 @@ You can also set _GIT_MERGE_VIEW to only show merge commits, example:
.PP .PP
.B export _GIT_MERGE_VIEW="exclusive" .B export _GIT_MERGE_VIEW="exclusive"
.PP .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 .PP
.B export _MENU_THEME=legacy .B export _MENU_THEME=legacy
.PP .PP
@@ -176,27 +187,13 @@ You can set _GIT_BRANCH to set the branch of the stats, example:
.PP .PP
.B export _GIT_BRANCH="master" .B export _GIT_BRANCH="master"
.PP .PP
.SH Calendar activity output
.PP
\fBactivity-calendar\fR outputs a visual grid of commit activity for a selected author, grouped by day-of-week (rows: Mon..Sun) and month (columns: Jan..Dec). Each cell is 3 characters wide, separated by one space.
.PP
Sample output:
.PP
.nf
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Mon ▓▓▓ ░░░ ▒▒▒ ░░░ ░░░ ▒▒▒ ▓▓▓ ░░░ ░░░ ▓▓▓ ▒▒▒ ▒▒▒
Tue ▒▒▒ ░░░ ▒▒▒ ░░░ ▒▒▒ ░░░ ▒▒▒ ▓▓▓ ▒▒▒ ░░░ ░░░ ░░░
Wed ░░░ ▓▓▓ ░░░ ▓▓▓ ▒▒▒ ░░░ ░░░ ▒▒▒ ░░░ ░░░ ▓▓▓ ░░░
Thu ░░░ ▒▒▒ ░░░ ░░░ ▒▒▒ ░░░ ▓▓▓ ▒▒▒ ▒▒▒ ░░░ ░░░ ▒▒▒
Fri ▒▒▒ ░░░ ▒▒▒ ▓▓▓ ░░░ ▓▓▓ ▒▒▒ ░░░ ▒▒▒ ░░░ ▒▒▒ ░░░
Sat ░░░ ░░░ ▒▒▒ ░░░ ░░░ ░░░ ▒▒▒ ░░░ ▒▒▒ ▓▓▓ ▒▒▒ ░░░
Sun ▓▓▓ ░░░ ▓▓▓ ░░░ ░░░ ▓▓▓ ░░░ ▒▒▒ ░░░ ░░░ ▓▓▓ ░░░
Legend: ... = 0 ░░░ = 12 ▒▒▒ = 35 ▓▓▓ = 6+ commits
.PP
You can set _GIT_IGNORE_AUTHORS to filter out specific authors, example: You can set _GIT_IGNORE_AUTHORS to filter out specific authors, example:
.PP .PP
.B export _GIT_IGNORE_AUTHORS="(author@examle.com|username)" .B export _GIT_IGNORE_AUTHORS="(author@examle.com|username)"
.PP
You can set _GIT_DAYS to set the number of days for the heatmap, example:
.PP
.B export _GIT_DAYS=30"
. .
.fi .fi

View File

@@ -11,7 +11,7 @@
. tests/assert.sh -v . tests/assert.sh -v
src="./git-quick-stats" src="./git-quick-stats"
assert "$src fail" "Invalid argument assert "$src fail" "Invalid argument: fail
NAME NAME
git-quick-stats - Simple and efficient way to access various stats in a git repo git-quick-stats - Simple and efficient way to access various stats in a git repo
@@ -73,6 +73,12 @@ LIST OPTIONS
-Z, --commits-by-author-by-timezone -Z, --commits-by-author-by-timezone
displays a list of commits per timezone by author displays a list of commits per timezone by author
CALENDAR OPTIONS
-k, --commits-calendar-by-author
shows a calendar heatmap of commits per day-of-week per month for a given author
-H, --commits-heatmap
shows a heatmap of commits per day-of-week per month for the last 30 days
SUGGEST OPTIONS SUGGEST OPTIONS
-r, --suggest-reviewers -r, --suggest-reviewers
show the best people to contact to review code show the best people to contact to review code
@@ -98,14 +104,18 @@ ADDITIONAL USAGE
You can set _GIT_BRANCH to set the branch of the stats You can set _GIT_BRANCH to set the branch of the stats
ex: export _GIT_BRANCH=master ex: export _GIT_BRANCH=master
You can set _GIT_IGNORE_AUTHORS to filter out specific authors 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\"
You can set _GIT_DAYS to set the number of days for the heatmap
ex: export _GIT_DAYS=30"
assert_raises "$src fail" 1 assert_raises "$src fail" 1
assert_contains "$src --suggest-reviewers" "Suggested code reviewers (based on git history)" assert_contains "$src --suggest-reviewers" "Suggested code reviewers (based on git history)"
assert_success "$src --suggest-reviewers" 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_success "$src --detailed-git-stats"
assert_contains "$src --commits-per-day" "Git commits per date" assert_contains "$src --commits-per-day" "Git commits per date"