Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Generate an annual commit report

Generate an annual commit report

Prints something like this:

In 2017 you made 2212 commits in 117 projects.

Commits per weekday
    Monday     313 *************************************
   Tuesday     339 *****************************************
 Wednesday     378 *********************************************
  Thursday     385 **********************************************
    Friday     487 ***********************************************************
  Saturday     177 *********************
    Sunday     133 ****************

Commits per month
       Jan     184 ***********************************
       Feb     226 *******************************************
       Mar     305 ***********************************************************
       Apr     146 ****************************
       May     120 ***********************
       Jun     129 ************************
       Jul      75 **************
       Aug     274 *****************************************************
       Sep     168 ********************************
       Oct     177 **********************************
       Nov     206 ***************************************
       Dec     202 ***************************************

Lines added (+) and removed (-) per weekday
    Monday   20216 +++++++
   Tuesday  157715 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 Wednesday   72366 ++++++++++++++++++++++++++
  Thursday   12006 ++++
    Friday   84441 +++++++++++++++++++++++++++++++
  Saturday    -624
    Sunday    9424 +++

Lines added (+) and removed (-) per month
       Jan    6238                            +
       Feb    3418
       Mar -149875 ---------------------------
       Apr  152817                            +++++++++++++++++++++++++++
       May    5851                            +
       Jun  175024                            +++++++++++++++++++++++++++++++
       Jul    3804
       Aug   22253                            ++++
       Sep    1733
       Oct    8275                            +
       Nov  127645                            +++++++++++++++++++++++
       Dec   -1639

Top 3 words in commit messages
         a     678 **********************************
       the     980 **************************************************
        to    1155 ***********************************************************

Configuration

If you keep your sources in ~/src you're all set up. If you like it different, simply invoke the script with the directories your sources live in:

$ ./annual.sh path/to/my/projects another/path

If you commit with more than just one eMail address, set an AUTHORS environment variable before invoking the script:

$ AUTHORS='alice@example.com ali@gmail.com' ./annual.sh

If you like to generate the report for a past year, set up YEAR:

$ YEAR=2010 ./annual.sh

If you want to replace "you" in the first line, set WHO:

$ WHO=Markus ./annual.sh
#!/usr/bin/env bash
# Print a bar chart from NAME VALUE pairs given on STDIN
chart() {
local TMP MIN=0 MAX=0 NAME VALUE
TMP=$(mktemp)
while read -r NAME VALUE
do
(( VALUE > MAX )) && MAX=$VALUE
(( VALUE < MIN )) && MIN=$VALUE
echo "$NAME" "$VALUE"
done > "$TMP"
local COLS=${COLS:-$(tput cols)}
(( COLS -= 21 ))
local PBAR=${POSITIVES:-'*'} NBAR=${NEGATIVES:-'-'} BLANK=' '
while (( ${#PBAR} < COLS ))
do
PBAR=$PBAR$PBAR
NBAR=$NBAR$NBAR
BLANK=$BLANK$BLANK
done
(( MIN < 0 )) && {
(( MAX -= MIN ))
(( MIN = -MIN ))
}
local F=$(( (MAX << 16) / COLS )) BAR
(( F < 1 )) && F=1
while read -r NAME VALUE
do
if (( VALUE > 0 ))
then
BAR=${BLANK:0:$(( (MIN << 16) / F ))}
BAR=$BAR${PBAR:0:$(( (VALUE << 16) / F ))}
else
BAR=${BLANK:0:$(( ((MIN + VALUE) << 16) / F ))}
BAR=$BAR${NBAR:0:$(( (-VALUE << 16) / F ))}
fi
printf '%10s %7s %s\n' \
"$NAME" \
"$VALUE" \
"$BAR"
done < "$TMP"
rm -rf "$TMP"
}
# Filter commits into days of the week
commits_per_weeday() {
local MON=0 TUE=0 WED=0 THU=0 FRI=0 SAT=0 SUN=0
while read -r DAY REST
do
case "$DAY" in
Mon*) (( ++MON )) ;;
Tue*) (( ++TUE )) ;;
Wed*) (( ++WED )) ;;
Thu*) (( ++THU )) ;;
Fri*) (( ++FRI )) ;;
Sat*) (( ++SAT )) ;;
Sun*) (( ++SUN )) ;;
esac
done
cat << EOF
Monday $MON
Tuesday $TUE
Wednesday $WED
Thursday $THU
Friday $FRI
Saturday $SAT
Sunday $SUN
EOF
}
# Filter commits into months
commits_per_month() {
local JAN=0 FEB=0 MAR=0 APR=0 MAY=0 JUN=0
local JUL=0 AUG=0 SEP=0 OCT=0 NOV=0 DEC=0
while read -r _ _ MONTH _
do
case "$MONTH" in
Jan*) (( ++JAN )) ;;
Feb*) (( ++FEB )) ;;
Mar*) (( ++MAR )) ;;
Apr*) (( ++APR )) ;;
May*) (( ++MAY )) ;;
Jun*) (( ++JUN )) ;;
Jul*) (( ++JUL )) ;;
Aug*) (( ++AUG )) ;;
Sep*) (( ++SEP )) ;;
Oct*) (( ++OCT )) ;;
Nov*) (( ++NOV )) ;;
Dec*) (( ++DEC )) ;;
esac
done
cat << EOF
Jan $JAN
Feb $FEB
Mar $MAR
Apr $APR
May $MAY
Jun $JUN
Jul $JUL
Aug $AUG
Sep $SEP
Oct $OCT
Nov $NOV
Dec $DEC
EOF
}
# Filter lines of code into days of the week
loc_per_weeday() {
local INSERTS=() DELETIONS=() DAY=7
while read -r FIRST SECOND _
do
case "$FIRST" in
'-') ;;
[0-9]*)
(( DAY > 6 )) && continue
(( FIRST > 0 )) && (( INSERTS[DAY] += FIRST ))
(( SECOND > 0 )) && (( DELETIONS[DAY] += SECOND ))
;;
Mon*) DAY=0;;
Tue*) DAY=1;;
Wed*) DAY=2;;
Thu*) DAY=3;;
Fri*) DAY=4;;
Sat*) DAY=5;;
Sun*) DAY=6;;
esac
done
cat << EOF
Monday $(( INSERTS[0] - DELETIONS[0] ))
Tuesday $(( INSERTS[1] - DELETIONS[1] ))
Wednesday $(( INSERTS[2] - DELETIONS[2] ))
Thursday $(( INSERTS[3] - DELETIONS[3] ))
Friday $(( INSERTS[4] - DELETIONS[4] ))
Saturday $(( INSERTS[5] - DELETIONS[5] ))
Sunday $(( INSERTS[6] - DELETIONS[6] ))
EOF
}
# Filter lines of code into months
loc_per_month() {
local INSERTS=() DELETIONS=() MONTH=12
while read -r FIRST SECOND REST
do
case "$FIRST" in
'-') ;;
[0-9]*)
(( MONTH > 11 )) && continue
(( FIRST > 0 )) && (( INSERTS[MONTH] += FIRST ))
(( SECOND > 0 )) && (( DELETIONS[MONTH] += SECOND ))
;;
*)
case "$REST" in
Jan*) MONTH=0;;
Feb*) MONTH=1;;
Mar*) MONTH=2;;
Apr*) MONTH=3;;
May*) MONTH=4;;
Jun*) MONTH=5;;
Jul*) MONTH=6;;
Aug*) MONTH=7;;
Sep*) MONTH=8;;
Oct*) MONTH=9;;
Nov*) MONTH=10;;
Dec*) MONTH=11;;
esac
;;
esac
done
cat << EOF
Jan $(( INSERTS[0] - DELETIONS[0] ))
Feb $(( INSERTS[1] - DELETIONS[1] ))
Mar $(( INSERTS[2] - DELETIONS[2] ))
Apr $(( INSERTS[3] - DELETIONS[3] ))
May $(( INSERTS[4] - DELETIONS[4] ))
Jun $(( INSERTS[5] - DELETIONS[5] ))
Jul $(( INSERTS[6] - DELETIONS[6] ))
Aug $(( INSERTS[7] - DELETIONS[7] ))
Sep $(( INSERTS[8] - DELETIONS[8] ))
Oct $(( INSERTS[9] - DELETIONS[9] ))
Nov $(( INSERTS[10] - DELETIONS[10] ))
Dec $(( INSERTS[11] - DELETIONS[11] ))
EOF
}
# Most used words in commit messages
top_words() {
grep -Eoa '[a-zA-Z0-9]+' |
sort |
uniq -c |
sort -n |
tail -n 3 | while read -r COUNT WORD
do
echo "$WORD $COUNT"
done
}
# Browse all git repositories and create an annual activity report
#
# ... - source directories (optional)
annual_report() {
local SINCE UNTIL
YEAR=${YEAR:-$(date +%Y)}
SINCE="${YEAR}-01-01"
UNTIL="$(( YEAR + 1 ))-01-01"
local AUTHOR AUTHOR_TAGS=''
for AUTHOR in ${AUTHORS:-$(git config user.email)}
do
AUTHOR_TAGS=$AUTHOR_TAGS${AUTHOR_TAGS:+' '}"--author=$AUTHOR"
done
local PROJECTS=0
local COMMITS=0
local CACHE_PREFIX="$PWD/${0##*/}-$$"
local CACHE_PATHS="${CACHE_PREFIX}-paths"
local CACHE_ALL="${CACHE_PREFIX}-all"
local CACHE_PROJECT="${CACHE_PREFIX}-project"
local CACHE_LOC="${CACHE_PREFIX}-loc"
local CACHE_MESSAGES="${CACHE_PREFIX}-messages"
rm_cache() {
rm -f "${CACHE_PREFIX}"*
}
rm_cache
local SRC
for SRC in "${@:-$HOME/src}"
do
SRC=${SRC%/}
if [ -d "$SRC" ]
then
find "$SRC" -type d -name '.git' > "$CACHE_PATHS"
else
cat "$SRC" > "$CACHE_PATHS" || exit $?
fi
local DIRECTORY
while read -r DIRECTORY
do
DIRECTORY=${DIRECTORY%/.git}
# $AUTHOR_TAGS must be expanded and cannot be quoted
# shellcheck disable=SC2086
(cd "$DIRECTORY" && [ -d '.git' ] &&
git log \
--branches \
--since="$SINCE" \
--until="$UNTIL" \
$AUTHOR_TAGS \
--format='tformat:%aD' > "$CACHE_PROJECT" &&
git log -c \
--numstat \
--branches \
--since="$SINCE" \
--until="$UNTIL" \
$AUTHOR_TAGS \
--format='tformat:%aD' >> "$CACHE_LOC"
git log \
--branches \
--since="$SINCE" \
--until="$UNTIL" \
$AUTHOR_TAGS \
--format='tformat:%B' >> "$CACHE_MESSAGES") || continue
local N
N=$(wc -l < "$CACHE_PROJECT")
(( N > 0 )) && {
cat "$CACHE_PROJECT" >> "$CACHE_ALL"
(( ++PROJECTS ))
(( COMMITS += N ))
}
done < "$CACHE_PATHS"
done
echo "In $YEAR ${WHO:-you} made $COMMITS commits in $PROJECTS projects."
echo
(( COMMITS > 0 )) && {
echo 'Commits per weekday'
commits_per_weeday < "$CACHE_ALL" | chart
echo
echo 'Commits per month'
commits_per_month < "$CACHE_ALL" | chart
echo
echo 'Lines added (+) and removed (-) per weekday'
loc_per_weeday < "$CACHE_LOC" | POSITIVES='+' chart
echo
echo 'Lines added (+) and removed (-) per month'
loc_per_month < "$CACHE_LOC" | POSITIVES='+' chart
echo
echo 'Top 3 words in commit messages'
top_words < "$CACHE_MESSAGES" | chart
echo
}
rm_cache
unset rm_cache
}
if [ "${BASH_SOURCE[0]}" == "$0" ]
then
annual_report "$@"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment