Compare commits

...

23 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
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
6 changed files with 1169 additions and 729 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

@ -8,7 +8,6 @@
![mainImageScreenshot](https://github.com/user-attachments/assets/7d8637a4-5a67-49f6-8724-ca7548b987c6) ![mainImageScreenshot](https://github.com/user-attachments/assets/7d8637a4-5a67-49f6-8724-ca7548b987c6)
## Table of Contents ## Table of Contents
[**Screenshots**](#screenshots) [**Screenshots**](#screenshots)
@ -61,7 +60,6 @@
![commitActivityScreenshot](https://github.com/user-attachments/assets/693fff31-65c7-4b9f-a011-6114a2d10a26) ![commitActivityScreenshot](https://github.com/user-attachments/assets/693fff31-65c7-4b9f-a011-6114a2d10a26)
## Usage ## Usage
### Interactive ### Interactive
@ -122,14 +120,16 @@ LIST OPTIONS
see a list of everyone who contributed to the repo see a list of everyone who contributed to the repo
-n, --new-contributors -n, --new-contributors
list everyone who made their first contribution since a specified date 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 -a, --commits-per-author
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
@ -143,6 +143,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
@ -169,6 +175,14 @@ You can set variable `_GIT_LIMIT` for limited output. It will affect the "change
export _GIT_LIMIT=20 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 ### Git log options
You can set `_GIT_LOG_OPTIONS` for [git log options](https://git-scm.com/docs/git-log#_options): You can set `_GIT_LOG_OPTIONS` for [git log options](https://git-scm.com/docs/git-log#_options):
@ -224,6 +238,14 @@ You can sort contribution stats by field `name`, `commits`, `insertions`, `delet
export _GIT_SORT_BY="name-asc" 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`.
@ -268,15 +290,16 @@ sudo make reinstall
### macOS (homebrew) ### macOS (homebrew)
macOS requires GNU coreutils to be installed and for the non "g" aliased `git-quick-stats` requires GNU `date`. On macOS, install GNU coreutils:
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.
```bash ```bash
brew install coreutils brew install coreutils
export PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH" 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: From there, you can install via Homebrew as follows:
```bash ```bash
@ -285,10 +308,20 @@ brew install git-quick-stats
Or you can follow the UNIX and Linux instructions if you wish. 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 If you would like to default to using GNU coreutils system-wide, add
can add `export PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"` to `export PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"` to your
your applicable `~/.bash_profile`, `~/.zprofile`, or other relevant profile applicable `~/.bash_profile`, `~/.zprofile`, or other relevant profile based
based on the shell of your choice. 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 ### Windows
@ -310,6 +343,7 @@ You can use the Docker image provided:
## System requirements ## System requirements
- An OS with a Bash shell - An OS with a Bash shell
- GNU `date` available as either `date` or `gdate`
- Tools we use: - Tools we use:
```bash ```bash
@ -397,24 +431,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,33 @@
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)
# 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 # 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)
@ -27,7 +54,7 @@ _until=${_GIT_UNTIL:-}
if [[ -n "${_until}" ]]; then if [[ -n "${_until}" ]]; then
_until="--until=$_until" _until="--until=$_until"
else 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 fi
# Set files or directories to be excluded in stats # Set files or directories to be excluded in stats
@ -84,6 +111,17 @@ if [[ ! "$_GIT_SORT_BY" =~ ^(name|commits|insertions|deletions|lines)-(asc|desc)
_GIT_SORT_BY="name-asc" _GIT_SORT_BY="name-asc"
fi 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
# Specific git tag
_tag="${_GIT_TAG:-}"
# 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}"
@ -102,17 +140,17 @@ function commitsCalendarByAuthor() {
# Gather commit counts # Gather commit counts
git -c log.showSignature=false log --use-mailmap $_merges \ git -c log.showSignature=false log --use-mailmap $_merges \
--date=iso --author="$author" "$_since" "$_until" $_log_options \ --date=iso --author="$author" "$_since" "$_until" $_log_options \
--pretty='%ad' $_pathspec | awk ' --pretty='%ad' $_pathspec | awk -v date_cmd="$_DATE_CMD" '
{ {
split($0, a, " "); split($0, a, " ");
# a[1] = YYYY-MM-DD # a[1] = YYYY-MM-DD
split(a[1], date_fields, "-"); split(a[1], date_fields, "-");
mon = date_fields[2] + 0; mon = date_fields[2] + 0;
cmd = "date -d \"" a[1] "\" +%u"; cmd = date_cmd " -d \"" a[1] "\" +%u";
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
@ -125,7 +163,7 @@ 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) if (c==0)
out="..."; out="...";
else if (c<=9) else if (c<=9)
@ -141,6 +179,93 @@ function commitsCalendarByAuthor() {
' '
} }
# 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_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_CMD -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
@ -151,7 +276,7 @@ function commitsCalendarByAuthor() {
################################################################################ ################################################################################
function checkUtils() { function checkUtils() {
readonly MSG="not found. Please make sure this is installed and in PATH." 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" sort tput tr uniq"
for u in $UTILS for u in $UTILS
@ -161,16 +286,13 @@ function checkUtils() {
# NOTE: The --version flag is only available in GNU date which is required # 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. # 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 # On FreeBSD and macOS the system date is BSD date; GNU date may be
# macOS and older distributions of Linux and Unix, a handful of helper # installed as 'gdate' via coreutils. _DATE_CMD is auto-detected above.
# functions can probably be created to handle every case of incompatibility if ! "$_DATE_CMD" --version >/dev/null 2>&1; then
# 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
echo "ERROR: GNU date is required." echo "ERROR: GNU date is required."
echo "If you're on macOS, please install it using 'brew install coreutils'." echo "On macOS, install it with: brew install coreutils"
echo "Ensure that your PATH is configured to use GNU date as well." 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." echo "See the README.md for further details."
exit 1 exit 1
fi fi
@ -235,6 +357,8 @@ LIST OPTIONS
see a list of everyone who contributed to the repo see a list of everyone who contributed to the repo
-n, --new-contributors -n, --new-contributors
list everyone who made their first contribution since a specified date 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 -a, --commits-per-author
displays a list of commits per author displays a list of commits per author
-d, --commits-per-day -d, --commits-per-day
@ -256,6 +380,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
@ -283,7 +413,9 @@ ADDITIONAL USAGE
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\" 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\"" 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"
} }
################################################################################ ################################################################################
@ -293,12 +425,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=""
@ -308,29 +434,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"
@ -343,22 +469,24 @@ function showMenu() {
printf %b "${NUMS} 9)${TEXT} All branches (sorted by most recent commit)\\n" 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} 10)${TEXT} All contributors (sorted by name)\\n"
printf %b "${NUMS} 11)${TEXT} New contributors (sorted by email)\\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} 12)${TEXT} New contributors since a tag\\n"
printf %b "${NUMS} 13)${TEXT} Git commits per date\\n" printf %b "${NUMS} 13)${TEXT} Git commits per author\\n"
printf %b "${NUMS} 14)${TEXT} Git commits per month\\n" printf %b "${NUMS} 14)${TEXT} Git commits per date\\n"
printf %b "${NUMS} 15)${TEXT} Git commits per year\\n" printf %b "${NUMS} 15)${TEXT} Git commits per month\\n"
printf %b "${NUMS} 16)${TEXT} Git commits per weekday\\n" printf %b "${NUMS} 16)${TEXT} Git commits per year\\n"
printf %b "${NUMS} 17)${TEXT} Git commits per weekday by author\\n" printf %b "${NUMS} 17)${TEXT} Git commits per weekday\\n"
printf %b "${NUMS} 18)${TEXT} Git commits per hour\\n" printf %b "${NUMS} 18)${TEXT} Git commits per weekday by author\\n"
printf %b "${NUMS} 19)${TEXT} Git commits per hour by author\\n" printf %b "${NUMS} 19)${TEXT} Git commits per hour\\n"
printf %b "${NUMS} 20)${TEXT} Git commits per timezone\\n" printf %b "${NUMS} 20)${TEXT} Git commits per hour by author\\n"
printf %b "${NUMS} 21)${TEXT} Git commits per timezone 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 "\\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 "\\n${TITLES} Calendar:\\n"
printf %b "${NUMS} 23)${TEXT} Activity calendar by author\\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 "\\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
} }
@ -440,7 +568,8 @@ function detailedGitStats() {
sort_command="sort ${sort_flags}" sort_command="sort ${sort_flags}"
# 1. git log -> awk (extract data) -> sort -> awk (format output with graphs) # 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" \ --pretty="format:commit %H%nAuthor: %aN <%aE>%nDate: %ad%n%n%w(0,4,4)%B%n" \
"$_since" "$_until" $_log_options $_pathspec | "$_since" "$_until" $_log_options $_pathspec |
LC_ALL=C awk ' LC_ALL=C awk '
@ -454,10 +583,31 @@ function detailedGitStats() {
if (last[author] == "") { last[author] = current_date; } if (last[author] == "") { last[author] = current_date; }
first[author] = current_date; first[author] = current_date;
} }
/^[0-9]/ { /file changed|files changed/ {
more[author] += $1; files = 0;
less[author] += $2; insertions = 0;
file[author] += 1; 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 { END {
for (author in commits) { for (author in commits) {
@ -564,7 +714,7 @@ function changelogs() {
local _author="" local _author=""
local commits="" local commits=""
local author="${1:-}" local author="${1:-}"
local next=$(date +%F) local next=$($_DATE_CMD +%F)
if [[ -z "${author}" ]]; then if [[ -z "${author}" ]]; then
optionPicked "Git changelogs:" optionPicked "Git changelogs:"
@ -604,19 +754,162 @@ function changelogs() {
################################################################################ ################################################################################
function myDailyStats() { function myDailyStats() {
optionPicked "My daily status:" 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 \ local -r my_name="$(git config user.name)"
--author="$(git config user.name)" $_merges \ local -r my_email="$(git config user.email)"
--since=$(date "+%Y-%m-%dT00:00:00") \ local -r today_start="$(date "+%Y-%m-%dT00:00:00")"
--until=$(date "+%Y-%m-%dT23:59:59") --reverse $_log_options \ local -r today_end="$(date "+%Y-%m-%dT23:59:59")"
| grep -cE "commit [a-f0-9]{40}") "commits" 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
} }
################################################################################ ################################################################################
@ -650,7 +943,7 @@ function csvOutput() {
printf "author,insertions,insertions_per,deletions,deletions_per,files," printf "author,insertions,insertions_per,deletions,deletions_per,files,"
printf "files_per,commits,commits_per,lines_changed,lines_changed_per\n" 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" \ --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 ' "$_since" "$_until" $_log_options $_pathspec | filter_ignored_authors | LC_ALL=C awk '
function printStats(author) { function printStats(author) {
@ -701,14 +994,34 @@ function csvOutput() {
if(last[author] == "" ) { last[author] = first[author] } if(last[author] == "" ) { last[author] = first[author] }
} }
/^[0-9]/ { /file changed|files changed/ {
more[author] += $1 files = 0
less[author] += $2 insertions = 0
deletions = 0
file[author] += 1 split($0, stats, ",")
more["total"] += $1 for (i in stats) {
less["total"] += $2 value = stats[i]
file["total"] += 1 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 { END {
@ -863,7 +1176,42 @@ function newContributors() {
local firstCommit=$(git -c log.showSignature=false log --author="$c" \ local firstCommit=$(git -c log.showSignature=false log --author="$c" \
--reverse --use-mailmap $_merges "$_since" "$_until" \ --reverse --use-mailmap $_merges "$_since" "$_until" \
--format='%at' $_log_options $_pathspec | filter_ignored_authors | head -n 1) --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" echo "$c"
fi fi
done done
@ -928,7 +1276,7 @@ function parse_year() {
# Handle the raw UNIX timestamp format i.e. 1697375696 +0000 # Handle the raw UNIX timestamp format i.e. 1697375696 +0000
if [[ "$date_str" =~ ^[0-9]+(\ [+-][0-9]{4})?$ ]]; then if [[ "$date_str" =~ ^[0-9]+(\ [+-][0-9]{4})?$ ]]; then
timestamp=$(echo "$date_str" | awk '{print $1}') 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 else
# Default case can get funky. We need to create a clever regex to # 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 # handle the default case which is like Mon Oct 15 12:34:56 2023 +0000
@ -949,7 +1297,7 @@ function parse_year() {
date_str=$(echo "$date_str" | awk -F'/' '{print $2"/"$1"/"$3}') date_str=$(echo "$date_str" | awk -F'/' '{print $2"/"$1"/"$3}')
fi fi
# Extract the final date # Extract the final date
year=$(date -d "$date_str" '+%Y' 2>/dev/null) year=$($_DATE_CMD -d "$date_str" '+%Y' 2>/dev/null)
fi fi
echo "$year" echo "$year"
@ -1245,11 +1593,20 @@ if [[ "$#" -eq 1 ]]; then
# what people may try. This script doesn't provide any additional # what people may try. This script doesn't provide any additional
# output for a bad date since `date`'s STDERR already contains # output for a bad date since `date`'s STDERR already contains
# useful information. # useful information.
if ! date -d "${newDate}" +%s > /dev/null 2>&1; then if ! $_DATE_CMD -d "${newDate}" +%s > /dev/null 2>&1; then
newDate="" newDate=""
fi fi
done done
newContributors "${newDate}";; 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;; -a|--commits-per-author) commitsPerAuthor;;
-d|--commits-per-day) commitsPerDay;; -d|--commits-per-day) commitsPerDay;;
-Y|--commits-by-year ) commitsByYear;; -Y|--commits-by-year ) commitsByYear;;
@ -1275,13 +1632,14 @@ 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;;
@ -1341,39 +1699,48 @@ if [[ "$#" -eq 0 ]]; then
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
if ! date -d "${newDate}" +%s > /dev/null 2>&1; then if ! $_DATE_CMD -d "${newDate}" +%s > /dev/null 2>&1; then
newDate="" newDate=""
fi fi
done done
newContributors "${newDate}"; showMenu;; newContributors "${newDate}"; showMenu;;
12) commitsPerAuthor; showMenu;; 12) while [[ -z "${_tag}" ]]; do
13) commitsPerDay; showMenu;; read -r -p "Which tag? " _tag
14) commitsByMonth; showMenu;; if ! git rev-parse "${_tag}" >/dev/null 2>&1; then
15) commitsByYear; showMenu;; echo "Error: Tag '${_tag}' does not exist."
16) commitsByWeekday; showMenu;; _tag=""
17) author="" 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 while [[ -z "${author}" ]]; do
read -r -p "Which author? " author read -r -p "Which author? " author
done done
commitsByWeekday "${author}"; showMenu;; commitsByWeekday "${author}"; showMenu;;
18) commitsByHour; showMenu;; 19) commitsByHour; showMenu;;
19) author="" 20) author=""
while [[ -z "${author}" ]]; do while [[ -z "${author}" ]]; do
read -r -p "Which author? " author read -r -p "Which author? " author
done done
commitsByHour "${author}"; showMenu;; commitsByHour "${author}"; showMenu;;
20) commitsByTimezone; showMenu;; 21) commitsByTimezone; showMenu;;
21) author="" 22) author=""
while [[ -z "${author}" ]]; do while [[ -z "${author}" ]]; do
read -r -p "Which author? " author read -r -p "Which author? " author
done done
commitsByTimezone "${author}"; showMenu;; commitsByTimezone "${author}"; showMenu;;
22) suggestReviewers; showMenu;; 23) suggestReviewers; showMenu;;
23) author="" 24) 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}"; showMenu;; commitsCalendarByAuthor "${author}"; showMenu;;
25) 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

View file

@ -17,8 +17,30 @@ and files. Extracting this information is not always trivial, mostly because
of a gadzillion options to a gadzillion git commands. of a gadzillion options to a gadzillion git commands.
This program allows you to see detailed information about a git repository. This program allows you to see detailed information about a git repository.
.PP .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 .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
@ -75,6 +97,11 @@ see a list of everyone who contributed to the repo
list everyone who made their first contribution since a specified date list everyone who made their first contribution since a specified date
.HP .HP
.PP .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 \fB\-a\fR, \fB\-\-commits\-per\-author\fR
.IP .IP
displays a list of commits per author displays a list of commits per author
@ -136,9 +163,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:
@ -149,6 +179,10 @@ You can set _GIT_LIMIT for limited output log, example:
.PP .PP
.B export _GIT_LIMIT=20 .B export _GIT_LIMIT=20
.PP .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: You can set _GIT_LOG_OPTIONS for git log options, example:
.PP .PP
.B export _GIT_LOG_OPTIONS="--ignore-all-space --ignore-blank-lines" .B export _GIT_LOG_OPTIONS="--ignore-all-space --ignore-blank-lines"
@ -180,27 +214,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

@ -52,6 +52,8 @@ LIST OPTIONS
see a list of everyone who contributed to the repo see a list of everyone who contributed to the repo
-n, --new-contributors -n, --new-contributors
list everyone who made their first contribution since a specified date 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 -a, --commits-per-author
displays a list of commits per author displays a list of commits per author
-d, --commits-per-day -d, --commits-per-day
@ -73,6 +75,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
@ -100,7 +108,9 @@ ADDITIONAL USAGE
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\" 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\"" 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
@ -113,6 +123,11 @@ 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"
assert_success "$src --commits-per-day" 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_startswith "$src --commits-by-year" "Git commits by year"
assert_success "$src --commits-by-year" assert_success "$src --commits-by-year"