Compare commits

...

10 commits

Author SHA1 Message Date
arzzen 0f9481a507
Merge pull request #199 from git-quick-stats/task/issues-3
Enhance daily statistics tracking
2026-04-18 14:15:20 +02:00
arzzen 0dc1fcf0c4 Refactor myDailyStats function for improved readability and performance 2026-04-13 12:15:27 +02:00
arzzen eebe147b30
Merge pull request #200 from git-quick-stats/task/issues-171
Add option to list new contributors since a specified git tag
2026-04-10 12:34:53 +02:00
arzzen 322470f415 docs: clarify tag-based contributor option behavior 2026-04-10 12:32:28 +02:00
arzzen 892040c3ad Add option to list new contributors since a specified git tag 2026-04-06 10:52:09 +02:00
arzzen b424bb9c17
Merge pull request #197 from git-quick-stats/fix/issues-196
GNU date requirements for macOS and FreeBSD
2026-04-06 10:33:02 +02:00
arzzen ec986e2cb6
Merge pull request #198 from git-quick-stats/fix/issues-148 2026-04-06 07:21:47 +02:00
arzzen 0f998185e8 Enhance my daily stats 2026-04-05 17:44:54 +02:00
arzzen 0133a4a998 Enhance detailedGitStats and csvOutput functions to include merge commit statistics 2026-04-05 17:31:17 +02:00
arzzen d407af3aba GNU date requirements for macOS and FreeBSD 2026-04-05 16:55:25 +02:00
4 changed files with 385 additions and 80 deletions

View file

@ -120,6 +120,8 @@ LIST OPTIONS
see a list of everyone who contributed to the repo
-n, --new-contributors
list everyone who made their first contribution since a specified date
-N, --new-contributors-since-tag
list everyone who made their first contribution since a specified git tag
-a, --commits-per-author
displays a list of commits per author
-d, --commits-per-day
@ -173,6 +175,14 @@ You can set variable `_GIT_LIMIT` for limited output. It will affect the "change
export _GIT_LIMIT=20
```
### Git tag
You can set `_GIT_TAG` to define the default tag used by the "new contributors since tag" option.
```bash
export _GIT_TAG="foo"
```
### Git log options
You can set `_GIT_LOG_OPTIONS` for [git log options](https://git-scm.com/docs/git-log#_options):
@ -280,15 +290,16 @@ sudo make reinstall
### macOS (homebrew)
macOS requires GNU coreutils to be installed and for the non "g" aliased
versions to be exported to your path. The following is an example of how to
perform this if you are using Homebrew as your package manager.
`git-quick-stats` requires GNU `date`. On macOS, install GNU coreutils:
```bash
brew install coreutils
export PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
```
The script auto-detects GNU date as either `date` or `gdate`, so exporting
`gnubin` is optional for this project.
From there, you can install via Homebrew as follows:
```bash
@ -297,10 +308,20 @@ brew install git-quick-stats
Or you can follow the UNIX and Linux instructions if you wish.
If you would like to default to using the GNU coreutils (recommended), then you
can add `export PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"` to
your applicable `~/.bash_profile`, `~/.zprofile`, or other relevant profile
based on the shell of your choice.
If you would like to default to using GNU coreutils system-wide, add
`export PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"` to your
applicable `~/.bash_profile`, `~/.zprofile`, or other relevant profile based
on the shell of your choice.
### FreeBSD
`git-quick-stats` requires GNU `date`. On FreeBSD, install GNU coreutils:
```bash
pkg install coreutils
```
FreeBSD provides GNU date as `gdate`, and the script auto-detects it.
### Windows
@ -322,6 +343,7 @@ You can use the Docker image provided:
## System requirements
- An OS with a Bash shell
- GNU `date` available as either `date` or `gdate`
- Tools we use:
```bash

View file

@ -28,6 +28,15 @@ COLOR_DARKEST_RED=$(tput setaf 52)
COLOR_GRAY=$(tput setaf 240)
COLOR_RESET=$(tput sgr0)
# Detect GNU date command (may be 'gdate' on FreeBSD/macOS with coreutils)
if date --version >/dev/null 2>&1; then
_DATE_CMD="date"
elif gdate --version >/dev/null 2>&1; then
_DATE_CMD="gdate"
else
_DATE_CMD="date"
fi
# Beginning git log date. Respects all git datetime formats
# 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)
@ -45,7 +54,7 @@ _until=${_GIT_UNTIL:-}
if [[ -n "${_until}" ]]; then
_until="--until=$_until"
else
_until="--until=$(LC_TIME=C date '+%a, %d %b %Y %H:%M:%S %Z')"
_until="--until=$(LC_TIME=C $_DATE_CMD '+%a, %d %b %Y %H:%M:%S %Z')"
fi
# Set files or directories to be excluded in stats
@ -110,6 +119,9 @@ if ! [[ "$_commit_days" =~ ^[0-9]+$ ]] || (( _commit_days <= 0 )); then
_commit_days=30
fi
# Specific git tag
_tag="${_GIT_TAG:-}"
# Default menu theme
# Set the legacy theme by typing "export _MENU_THEME=legacy"
_theme="${_MENU_THEME:=default}"
@ -128,13 +140,13 @@ function commitsCalendarByAuthor() {
# Gather commit counts
git -c log.showSignature=false log --use-mailmap $_merges \
--date=iso --author="$author" "$_since" "$_until" $_log_options \
--pretty='%ad' $_pathspec | awk '
--pretty='%ad' $_pathspec | awk -v date_cmd="$_DATE_CMD" '
{
split($0, a, " ");
# a[1] = YYYY-MM-DD
split(a[1], date_fields, "-");
mon = date_fields[2] + 0;
cmd = "date -d \"" a[1] "\" +%u";
cmd = date_cmd " -d \"" a[1] "\" +%u";
cmd | getline weekday;
close(cmd);
# weekday: 1=Mon, ..., 7=Sun
@ -204,14 +216,14 @@ function commitsHeatmap() {
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
local day=$($_DATE_CMD -d "-$i days" +"%Y-%m-%d")
if [[ $($_DATE_CMD -d "$day" +%u) -gt 5 ]]; then
echo -en "${COLOR_GRAY}"
else
echo -en "${COLOR_RESET}"
fi
local dayName=$(date -d "$day" +%a)
local dayName=$($_DATE_CMD -d "$day" +%a)
printf "%s | %s |" "$dayName" "$day"
declare -a commits_per_hour
@ -264,7 +276,7 @@ function commitsHeatmap() {
################################################################################
function checkUtils() {
readonly MSG="not found. Please make sure this is installed and in PATH."
readonly UTILS="awk basename cat column date echo git grep head printf seq \
readonly UTILS="awk basename cat column echo git grep head printf seq \
sort tput tr uniq"
for u in $UTILS
@ -274,16 +286,13 @@ function checkUtils() {
# NOTE: The --version flag is only available in GNU date which is required
# for how the current date/time strings are used in this shell script.
# To fully support the legacy BSD date found in a default install within
# macOS and older distributions of Linux and Unix, a handful of helper
# functions can probably be created to handle every case of incompatibility
# between the two. Until that's implemented, it is probably best to warn
# the user that this will not work rather than having it silently bomb out
# during runtime.
if ! date --version >/dev/null 2>&1; then
# On FreeBSD and macOS the system date is BSD date; GNU date may be
# installed as 'gdate' via coreutils. _DATE_CMD is auto-detected above.
if ! "$_DATE_CMD" --version >/dev/null 2>&1; then
echo "ERROR: GNU date is required."
echo "If you're on macOS, please install it using 'brew install coreutils'."
echo "Ensure that your PATH is configured to use GNU date as well."
echo "On macOS, install it with: brew install coreutils"
echo "On FreeBSD, install it with: pkg install coreutils (provides gdate)"
echo "Ensure that GNU date (or gdate) is in your PATH."
echo "See the README.md for further details."
exit 1
fi
@ -348,6 +357,8 @@ LIST OPTIONS
see a list of everyone who contributed to the repo
-n, --new-contributors
list everyone who made their first contribution since a specified date
-N, --new-contributors-since-tag
list everyone who made their first contribution since a specified tag
-a, --commits-per-author
displays a list of commits per author
-d, --commits-per-day
@ -458,21 +469,22 @@ function showMenu() {
printf %b "${NUMS} 9)${TEXT} All branches (sorted by most recent commit)\\n"
printf %b "${NUMS} 10)${TEXT} All contributors (sorted by name)\\n"
printf %b "${NUMS} 11)${TEXT} New contributors (sorted by email)\\n"
printf %b "${NUMS} 12)${TEXT} Git commits per author\\n"
printf %b "${NUMS} 13)${TEXT} Git commits per date\\n"
printf %b "${NUMS} 14)${TEXT} Git commits per month\\n"
printf %b "${NUMS} 15)${TEXT} Git commits per year\\n"
printf %b "${NUMS} 16)${TEXT} Git commits per weekday\\n"
printf %b "${NUMS} 17)${TEXT} Git commits per weekday by author\\n"
printf %b "${NUMS} 18)${TEXT} Git commits per hour\\n"
printf %b "${NUMS} 19)${TEXT} Git commits per hour by author\\n"
printf %b "${NUMS} 20)${TEXT} Git commits per timezone\\n"
printf %b "${NUMS} 21)${TEXT} Git commits per timezone by author\\n"
printf %b "${NUMS} 12)${TEXT} New contributors since a tag\\n"
printf %b "${NUMS} 13)${TEXT} Git commits per author\\n"
printf %b "${NUMS} 14)${TEXT} Git commits per date\\n"
printf %b "${NUMS} 15)${TEXT} Git commits per month\\n"
printf %b "${NUMS} 16)${TEXT} Git commits per year\\n"
printf %b "${NUMS} 17)${TEXT} Git commits per weekday\\n"
printf %b "${NUMS} 18)${TEXT} Git commits per weekday by author\\n"
printf %b "${NUMS} 19)${TEXT} Git commits per hour\\n"
printf %b "${NUMS} 20)${TEXT} Git commits per hour by author\\n"
printf %b "${NUMS} 21)${TEXT} Git commits per timezone\\n"
printf %b "${NUMS} 22)${TEXT} Git commits per timezone by author\\n"
printf %b "\\n${TITLES} Suggest:\\n"
printf %b "${NUMS} 22)${TEXT} Code reviewers (based on git history)\\n"
printf %b "${NUMS} 23)${TEXT} Code reviewers (based on git history)\\n"
printf %b "\\n${TITLES} Calendar:\\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 "${NUMS} 24)${TEXT} Activity calendar by author\\n"
printf %b "${NUMS} 25)${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 "${TEXT}> ${COLOR_NORMAL}"
read -r opt
@ -556,7 +568,8 @@ function detailedGitStats() {
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 \
# Use --shortstat so merge commits contribute insertion/deletion/file counts.
git -c log.showSignature=false log ${_branch} --use-mailmap $_merges --shortstat --cc \
--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 '
@ -570,10 +583,31 @@ function detailedGitStats() {
if (last[author] == "") { last[author] = current_date; }
first[author] = current_date;
}
/^[0-9]/ {
more[author] += $1;
less[author] += $2;
file[author] += 1;
/file changed|files changed/ {
files = 0;
insertions = 0;
deletions = 0;
split($0, stats, ",");
for (i in stats) {
value = stats[i];
gsub(/^ +| +$/, "", value);
if (value ~ /file changed|files changed/) {
gsub(/[^0-9]/, "", value);
files = value + 0;
} else if (value ~ /insertion/) {
gsub(/[^0-9]/, "", value);
insertions = value + 0;
} else if (value ~ /deletion/) {
gsub(/[^0-9]/, "", value);
deletions = value + 0;
}
}
more[author] += insertions;
less[author] += deletions;
file[author] += files;
}
END {
for (author in commits) {
@ -680,7 +714,7 @@ function changelogs() {
local _author=""
local commits=""
local author="${1:-}"
local next=$(date +%F)
local next=$($_DATE_CMD +%F)
if [[ -z "${author}" ]]; then
optionPicked "Git changelogs:"
@ -720,19 +754,162 @@ function changelogs() {
################################################################################
function myDailyStats() {
optionPicked "My daily status:"
git diff --shortstat '@{0 day ago}' | sort -nr | tr ',' '\n' | LC_ALL=C awk '
{ args[NR] = $0; }
END {
for (i = 1; i <= NR; ++i) {
printf "\t%s\n", args[i]
}
}'
echo -e "\t" $(git -c log.showSignature=false log --use-mailmap \
--author="$(git config user.name)" $_merges \
--since=$(date "+%Y-%m-%dT00:00:00") \
--until=$(date "+%Y-%m-%dT23:59:59") --reverse $_log_options \
| grep -cE "commit [a-f0-9]{40}") "commits"
local -r my_name="$(git config user.name)"
local -r my_email="$(git config user.email)"
local -r today_start="$(date "+%Y-%m-%dT00:00:00")"
local -r today_end="$(date "+%Y-%m-%dT23:59:59")"
local -r yesterday_start="$(date -d "yesterday 00:00:00" "+%Y-%m-%dT%H:%M:%S")"
local -r yesterday_end="$(date -d "yesterday 23:59:59" "+%Y-%m-%dT%H:%M:%S")"
local -r today_date="$(date "+%Y-%m-%d")"
local -r yesterday_date="$(date -d "yesterday" "+%Y-%m-%d")"
# Current user's totals for today
local -r my_commits_today="$(git rev-list --count --author="$my_name" $_merges \
--since="$today_start" --until="$today_end" HEAD)"
local -r my_today_stats="$(git -c log.showSignature=false log --use-mailmap $_merges \
--author="$my_name" --numstat --format='' \
--since="$today_start" --until="$today_end" $_log_options $_pathspec \
| LC_ALL=C awk -F'\t' '
$1 ~ /^[0-9]+$/ && $2 ~ /^[0-9]+$/ {
ins += $1;
del += $2;
files[$3] = 1;
}
END {
for (f in files) {
file_count++;
}
printf "%d|%d|%d", file_count + 0, ins + 0, del + 0;
}
')"
local -r my_files_today="$(echo "$my_today_stats" | awk -F'|' '{print $1}')"
local -r my_insertions_today="$(echo "$my_today_stats" | awk -F'|' '{print $2}')"
local -r my_deletions_today="$(echo "$my_today_stats" | awk -F'|' '{print $3}')"
echo -e "\tfor ${my_name} (${today_date}):"
echo -e "\t commits: ${my_commits_today}"
echo -e "\t files modified: ${my_files_today}"
echo -e "\t insertions: ${my_insertions_today}"
echo -e "\t deletions: ${my_deletions_today}"
# Compare repository totals between yesterday and today
local -r today_stats="$(git -c log.showSignature=false log --use-mailmap $_merges \
--numstat --format='%H' --since="$today_start" --until="$today_end" \
$_log_options $_pathspec | LC_ALL=C awk -F'\t' '
/^[0-9a-f]{40}$/ { commits++; next }
$1 ~ /^[0-9]+$/ && $2 ~ /^[0-9]+$/ {
ins += $1;
del += $2;
files[$3] = 1;
}
END {
for (f in files) {
file_count++;
}
printf "%d|%d|%d|%d", commits + 0, file_count + 0, ins + 0, del + 0;
}
')"
local -r yday_stats="$(git -c log.showSignature=false log --use-mailmap $_merges \
--numstat --format='%H' --since="$yesterday_start" --until="$yesterday_end" \
$_log_options $_pathspec | LC_ALL=C awk -F'\t' '
/^[0-9a-f]{40}$/ { commits++; next }
$1 ~ /^[0-9]+$/ && $2 ~ /^[0-9]+$/ {
ins += $1;
del += $2;
files[$3] = 1;
}
END {
for (f in files) {
file_count++;
}
printf "%d|%d|%d|%d", commits + 0, file_count + 0, ins + 0, del + 0;
}
')"
local -r today_commits="$(echo "$today_stats" | awk -F'|' '{print $1}')"
local -r today_files="$(echo "$today_stats" | awk -F'|' '{print $2}')"
local -r today_insertions="$(echo "$today_stats" | awk -F'|' '{print $3}')"
local -r today_deletions="$(echo "$today_stats" | awk -F'|' '{print $4}')"
local -r yday_commits="$(echo "$yday_stats" | awk -F'|' '{print $1}')"
local -r yday_files="$(echo "$yday_stats" | awk -F'|' '{print $2}')"
local -r yday_insertions="$(echo "$yday_stats" | awk -F'|' '{print $3}')"
local -r yday_deletions="$(echo "$yday_stats" | awk -F'|' '{print $4}')"
echo -e "\n\trepo comparison (${yesterday_date} -> ${today_date}):"
echo -e "\t commits: ${yday_commits} -> ${today_commits} (delta: $((today_commits - yday_commits)))"
echo -e "\t files modified: ${yday_files} -> ${today_files} (delta: $((today_files - yday_files)))"
echo -e "\t insertions: ${yday_insertions} -> ${today_insertions} (delta: $((today_insertions - yday_insertions)))"
echo -e "\t deletions: ${yday_deletions} -> ${today_deletions} (delta: $((today_deletions - yday_deletions)))"
# Most changed files today (all authors)
echo -e "\n\tmost changed files today:"
local -r top_changed_files="$(git -c log.showSignature=false log --use-mailmap $_merges \
--numstat --format='' --since="$today_start" --until="$today_end" \
$_log_options $_pathspec | LC_ALL=C awk -F'\t' '
$1 ~ /^[0-9]+$/ && $2 ~ /^[0-9]+$/ {
files[$3] += ($1 + $2);
ins[$3] += $1;
del[$3] += $2;
}
END {
for (f in files) {
printf "%d\t%d\t%d\t%s\n", files[f], ins[f], del[f], f;
}
}
' | sort -nr | head -n 5)"
if [[ -n "$top_changed_files" ]]; then
echo "$top_changed_files" | LC_ALL=C awk -F'\t' '{
printf "\t %s (total: %d, +%d, -%d)\n", $4, $1, $2, $3;
}'
else
echo -e "\t no changed files found for today"
fi
# First and last commit of today
local -r first_today_commit="$(git -c log.showSignature=false log --use-mailmap $_merges \
--reverse --since="$today_start" --until="$today_end" \
--date=iso --format='%ad | %aN | %s' $_log_options $_pathspec | head -n 1)"
local -r last_today_commit="$(git -c log.showSignature=false log --use-mailmap $_merges \
--since="$today_start" --until="$today_end" \
--date=iso --format='%ad | %aN | %s' $_log_options $_pathspec | head -n 1)"
echo -e "\n\ttoday commit window:"
if [[ -n "$first_today_commit" ]]; then
echo -e "\t first commit: ${first_today_commit}"
echo -e "\t last commit: ${last_today_commit}"
else
echo -e "\t no commits found for today"
fi
# Most active users today (excluding current user)
echo -e "\n\tmost active users today (excluding you):"
local -r active_users="$(git -c log.showSignature=false log --use-mailmap $_merges \
--since="$today_start" --until="$today_end" --format='%aN|%aE' \
$_log_options $_pathspec | LC_ALL=C awk -F'|' -v git_name="$my_name" -v git_email="$my_email" '
$1 != git_name && $2 != git_email {
key = $1 " <" $2 ">";
count[key]++;
}
END {
for (k in count) {
printf "%d\t%s\n", count[k], k;
}
}
' | sort -nr | head -n 5)"
if [[ -n "$active_users" ]]; then
echo "$active_users" | LC_ALL=C awk -F'\t' '{
printf "\t %s commits - %s\n", $1, $2;
}'
else
echo -e "\t no additional active users today"
fi
}
################################################################################
@ -766,7 +943,7 @@ function csvOutput() {
printf "author,insertions,insertions_per,deletions,deletions_per,files,"
printf "files_per,commits,commits_per,lines_changed,lines_changed_per\n"
git -c log.showSignature=false log ${_branch} --use-mailmap $_merges --numstat \
git -c log.showSignature=false log ${_branch} --use-mailmap $_merges --shortstat --cc \
--pretty="format:commit %H%nAuthor: %aN <%aE>%nDate: %ad%n%n%w(0,4,4)%B%n" \
"$_since" "$_until" $_log_options $_pathspec | filter_ignored_authors | LC_ALL=C awk '
function printStats(author) {
@ -817,14 +994,34 @@ function csvOutput() {
if(last[author] == "" ) { last[author] = first[author] }
}
/^[0-9]/ {
more[author] += $1
less[author] += $2
/file changed|files changed/ {
files = 0
insertions = 0
deletions = 0
file[author] += 1
more["total"] += $1
less["total"] += $2
file["total"] += 1
split($0, stats, ",")
for (i in stats) {
value = stats[i]
gsub(/^ +| +$/, "", value)
if (value ~ /file changed|files changed/) {
gsub(/[^0-9]/, "", value)
files = value + 0
} else if (value ~ /insertion/) {
gsub(/[^0-9]/, "", value)
insertions = value + 0
} else if (value ~ /deletion/) {
gsub(/[^0-9]/, "", value)
deletions = value + 0
}
}
more[author] += insertions
less[author] += deletions
file[author] += files
more["total"] += insertions
less["total"] += deletions
file["total"] += files
}
END {
@ -979,7 +1176,42 @@ function newContributors() {
local firstCommit=$(git -c log.showSignature=false log --author="$c" \
--reverse --use-mailmap $_merges "$_since" "$_until" \
--format='%at' $_log_options $_pathspec | filter_ignored_authors | head -n 1)
if [[ $firstCommit -ge $(date -d "$newDate" +%s) ]]; then
if [[ $firstCommit -ge $($_DATE_CMD -d "$newDate" +%s) ]]; then
echo "$c"
fi
done
}
################################################################################
# DESC: Lists all new contributors to a repo since a specified git tag
# ARGS: $tag (required): Git tag to use as the cutoff
# OUTS: None
################################################################################
function newContributorsSinceTag() {
local tag="${1:-}"
[[ -z "$tag" ]] && { echo "Usage: newContributorsSinceTag <tag>"; return 1; }
if ! git rev-parse "$tag" >/dev/null 2>&1; then
echo "Error: Tag '$tag' does not exist."
return 1
fi
local tagDate=$(git log -n 1 --format='%ai' "$tag" | cut -d' ' -f1)
if [[ -z "$tagDate" ]]; then
echo "Error: Could not determine date for tag '$tag'."
return 1
fi
optionPicked "New contributors since tag '$tag' ($tagDate):"
local contributors=$(git -c log.showSignature=false log --use-mailmap $_merges \
"$_since" "$_until" --format='%aE' $_log_options \
$_pathspec | sort -u)
for c in $contributors; do
local firstCommit=$(git -c log.showSignature=false log --author="$c" \
--reverse --use-mailmap $_merges "$_since" "$_until" \
--format='%at' $_log_options $_pathspec | filter_ignored_authors | head -n 1)
if [[ $firstCommit -ge $($_DATE_CMD -d "$tagDate" +%s) ]]; then
echo "$c"
fi
done
@ -1044,7 +1276,7 @@ function parse_year() {
# Handle the raw UNIX timestamp format i.e. 1697375696 +0000
if [[ "$date_str" =~ ^[0-9]+(\ [+-][0-9]{4})?$ ]]; then
timestamp=$(echo "$date_str" | awk '{print $1}')
year=$(date -d "@$timestamp" '+%Y' 2>/dev/null)
year=$($_DATE_CMD -d "@$timestamp" '+%Y' 2>/dev/null)
else
# Default case can get funky. We need to create a clever regex to
# handle the default case which is like Mon Oct 15 12:34:56 2023 +0000
@ -1065,7 +1297,7 @@ function parse_year() {
date_str=$(echo "$date_str" | awk -F'/' '{print $2"/"$1"/"$3}')
fi
# Extract the final date
year=$(date -d "$date_str" '+%Y' 2>/dev/null)
year=$($_DATE_CMD -d "$date_str" '+%Y' 2>/dev/null)
fi
echo "$year"
@ -1361,11 +1593,20 @@ if [[ "$#" -eq 1 ]]; then
# what people may try. This script doesn't provide any additional
# output for a bad date since `date`'s STDERR already contains
# useful information.
if ! date -d "${newDate}" +%s > /dev/null 2>&1; then
if ! $_DATE_CMD -d "${newDate}" +%s > /dev/null 2>&1; then
newDate=""
fi
done
newContributors "${newDate}";;
-N|--new-contributors-since-tag)
while [[ -z "${_tag}" ]]; do
read -r -p "Which tag? " _tag
if ! git rev-parse "${_tag}" >/dev/null 2>&1; then
echo "Error: Tag '${_tag}' does not exist."
_tag=""
fi
done
newContributorsSinceTag "${_tag}";;
-a|--commits-per-author) commitsPerAuthor;;
-d|--commits-per-day) commitsPerDay;;
-Y|--commits-by-year ) commitsByYear;;
@ -1458,40 +1699,48 @@ if [[ "$#" -eq 0 ]]; then
11) newDate=""
while [[ -z "${newDate}" ]]; do
read -r -p "Since what date? (e.g. '2023-04-13', '13 April 2023', 'last Thursday') " newDate
if ! date -d "${newDate}" +%s > /dev/null 2>&1; then
if ! $_DATE_CMD -d "${newDate}" +%s > /dev/null 2>&1; then
newDate=""
fi
done
newContributors "${newDate}"; showMenu;;
12) commitsPerAuthor; showMenu;;
13) commitsPerDay; showMenu;;
14) commitsByMonth; showMenu;;
15) commitsByYear; showMenu;;
16) commitsByWeekday; showMenu;;
17) author=""
12) while [[ -z "${_tag}" ]]; do
read -r -p "Which tag? " _tag
if ! git rev-parse "${_tag}" >/dev/null 2>&1; then
echo "Error: Tag '${_tag}' does not exist."
_tag=""
fi
done
newContributorsSinceTag "${_tag}"; showMenu;;
13) commitsPerAuthor; showMenu;;
14) commitsPerDay; showMenu;;
15) commitsByMonth; showMenu;;
16) commitsByYear; showMenu;;
17) commitsByWeekday; showMenu;;
18) author=""
while [[ -z "${author}" ]]; do
read -r -p "Which author? " author
done
commitsByWeekday "${author}"; showMenu;;
18) commitsByHour; showMenu;;
19) author=""
19) commitsByHour; showMenu;;
20) author=""
while [[ -z "${author}" ]]; do
read -r -p "Which author? " author
done
commitsByHour "${author}"; showMenu;;
20) commitsByTimezone; showMenu;;
21) author=""
21) commitsByTimezone; showMenu;;
22) author=""
while [[ -z "${author}" ]]; do
read -r -p "Which author? " author
done
commitsByTimezone "${author}"; showMenu;;
22) suggestReviewers; showMenu;;
23) author=""
23) suggestReviewers; showMenu;;
24) author=""
while [[ -z "${author}" ]]; do
read -r -p "Which author? " author
done
commitsCalendarByAuthor "${author}"; showMenu;;
24) commitsHeatmap; showMenu;;
25) commitsHeatmap; showMenu;;
q|"\n") exit;;
*) clear; optionPicked "Pick an option from the menu"; showMenu;;
esac

View file

@ -17,6 +17,24 @@ and files. Extracting this information is not always trivial, mostly because
of a gadzillion options to a gadzillion git commands.
This program allows you to see detailed information about a git repository.
.PP
.SH PLATFORM NOTES
.PP
git-quick-stats requires GNU date support for date parsing options.
.PP
On macOS, install GNU coreutils with:
.PP
.B brew install coreutils
.PP
On FreeBSD, install GNU coreutils with:
.PP
.B pkg install coreutils
.PP
GNU date may be available as either
.B date
or
.B gdate
and git-quick-stats auto-detects both.
.PP
.SH GENERATE OPTIONS
.PP
\fB\-h\fR, \-?, \fB\-\-help\fR
@ -79,6 +97,11 @@ see a list of everyone who contributed to the repo
list everyone who made their first contribution since a specified date
.HP
.PP
\fB\-N\fR, \fB\-\-new\-contributors\-since\-tag\fR
.IP
list everyone who made their first contribution since a specified git tag
.HP
.PP
\fB\-a\fR, \fB\-\-commits\-per\-author\fR
.IP
displays a list of commits per author
@ -156,6 +179,10 @@ You can set _GIT_LIMIT for limited output log, example:
.PP
.B export _GIT_LIMIT=20
.PP
You can set _GIT_TAG to define the default tag used by the "new contributors since tag" option, example:
.PP
.B export _GIT_TAG="foo"
.PP
You can set _GIT_LOG_OPTIONS for git log options, example:
.PP
.B export _GIT_LOG_OPTIONS="--ignore-all-space --ignore-blank-lines"

View file

@ -52,6 +52,8 @@ LIST OPTIONS
see a list of everyone who contributed to the repo
-n, --new-contributors
list everyone who made their first contribution since a specified date
-N, --new-contributors-since-tag
list everyone who made their first contribution since a specified tag
-a, --commits-per-author
displays a list of commits per author
-d, --commits-per-day
@ -121,6 +123,11 @@ assert_success "$src --detailed-git-stats"
assert_contains "$src --commits-per-day" "Git commits per date"
assert_success "$src --commits-per-day"
assert_contains "$src --my-daily-stats" "My daily status"
assert_contains "$src --my-daily-stats" "most changed files today"
assert_contains "$src --my-daily-stats" "most active users today (excluding you)"
assert_success "$src --my-daily-stats"
assert_startswith "$src --commits-by-year" "Git commits by year"
assert_success "$src --commits-by-year"