Files
git-quick-stats-mirror/git-quick-stats
Tom Ice 2274ca1284 Improve color compatibility and other minor fixes
* Color was originally done with ANSI escape characters for defining
  different "expected" colors. However, this is not uniform across all
  terminals. To improve what the designers of this program expect
  colors to be, escape codes were replaced with tput equivalents.
  For more information, see the GNU manual here:

  https://www.gnu.org/software/termutils/manual/termutils-2.0/html_chapter/tput_1.html

* Limited scope of variables to their local scope instead of having them
  be global

* Renamed menu variables to aid in readability and adjusted formatting
  slightly to be more uniform

* Fixed a bug where option_picked was assigning an array to a string
  and relying on the default behavior of the shell to interpret it

* Added the -r option to "read" for safety, as read without -r will
  interpret backslashes before spaces/line feeds, which tends to
  be an unintended side effect

* Updated all backtick notation to the newer POSIX $(..) notation for
  aid in readability when paired next to single quotes and improved
  safety

* Updated the README.md to include missing utilities and fixed some
  minor formatting issues
2018-12-21 12:05:53 -05:00

439 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
set -o nounset
set -o errexit
_since=${_GIT_SINCE:-}
if [ ! -z ${_since} ]
then _since="--since=$_since"
fi
_until=${_GIT_UNTIL:-}
if [ ! -z ${_until} ]
then _until="--until=$_until"
fi
_pathspec=${_GIT_PATHSPEC:-}
if [ ! -z "${_pathspec}" ]
then _pathspec="-- $_pathspec"
fi
_limit=${_GIT_LIMIT:-}
if [ ! -z ${_limit} ]
then _limit=$_limit
else
_limit=10
fi
function show_menu() {
local NORMAL=$(tput sgr0)
local CYAN_TEXT=$(tput setaf 6)
local RED_TEXT=$(tput setaf 1)
local YELLOW_TEXT=$(tput setaf 3)
echo -e "\n${RED_TEXT} Generate: ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 1)${CYAN_TEXT} Contribution stats (by author) ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 2)${CYAN_TEXT} Git changelogs (last $_limit days)${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 3)${CYAN_TEXT} Git changelogs by author ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 4)${CYAN_TEXT} My daily status ${NORMAL}"
echo -e "${RED_TEXT} List: ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 5)${CYAN_TEXT} Branch tree view (last $_limit)${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 6)${CYAN_TEXT} All branches (sorted by most recent commit) ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 7)${CYAN_TEXT} All contributors (sorted by name) ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 8)${CYAN_TEXT} Git commits per author ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 9)${CYAN_TEXT} Git commits per date ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 10)${CYAN_TEXT} Git commits per month ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 11)${CYAN_TEXT} Git commits per weekday ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 12)${CYAN_TEXT} Git commits per hour ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 13)${CYAN_TEXT} Git commits by author per hour ${NORMAL}"
echo -e "${RED_TEXT} Suggest: ${NORMAL}"
echo -e "${CYAN_TEXT} ${YELLOW_TEXT} 14)${CYAN_TEXT} Code reviewers (based on git history) ${NORMAL}"
echo -e "\n${YELLOW_TEXT}Please enter a menu option or ${RED_TEXT}press enter to exit. ${NORMAL}"
read -r opt
}
function option_picked() {
local BOLD=$(tput bold)
local RED_TEXT=$(tput setaf 1)
local COLOR="${BOLD}${RED_TEXT}"
local RESET=$(tput sgr0)
local MESSAGE=${*:-"${RESET}Error: No message passed"}
echo -e "${COLOR}${MESSAGE}${RESET}\n"
}
function detailedGitStats() {
option_picked "Contribution stats (by author):"
git log --use-mailmap --no-merges --numstat --pretty="format:commit %H%nAuthor: %aN <%aE>%nDate: %ad%n%n%w(0,4,4)%B%n" $_since $_until $_pathspec | LC_ALL=C awk '
function printStats(author) {
printf "\t%s:\n", author
if( more["total"] > 0 ) {
printf "\t insertions: %d (%.0f%%)\n", more[author], (more[author] / more["total"] * 100)
}
if( less["total"] > 0 ) {
printf "\t deletions: %d (%.0f%%)\n", less[author], (less[author] / less["total"] * 100)
}
if( file["total"] > 0 ) {
printf "\t files: %d (%.0f%%)\n", file[author], (file[author] / file["total"] * 100)
}
if(commits["total"] > 0) {
printf "\t commits: %d (%.0f%%)\n", commits[author], (commits[author] / commits["total"] * 100)
}
if ( first[author] != "" ) {
printf "\t lines changed: %s\n", more[author] + less[author]
printf "\t first commit: %s\n", first[author]
printf "\t last commit: %s\n", last[author]
}
printf "\n"
}
/^Author:/ {
$1 = ""
author = $0
commits[author] += 1
commits["total"] += 1
}
/^Date:/ {
$1="";
first[author] = substr($0, 2)
if(last[author] == "" ) { last[author] = first[author] }
}
/^[0-9]/ {
more[author] += $1
less[author] += $2
file[author] += 1
more["total"] += $1
less["total"] += $2
file["total"] += 1
}
END {
for (author in commits) {
if (author != "total") {
printStats(author)
}
}
printStats("total")
}'
}
function suggestReviewers() {
option_picked "Suggested code reviewers (based on git history):"
git log --use-mailmap --no-merges $_since $_until --pretty=%aN $_pathspec $* | head -n 100 | sort | uniq -c | sort -nr | LC_ALL=C awk '
{ args[NR] = $0; }
END {
for (i = 1; i <= NR; ++i) {
printf "%s\n", args[i]
}
}' | column -t -s,
}
function commitsByMonth() {
option_picked "Git commits by month:"
echo -e "\tmonth\tsum"
for i in Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
do
echo -en "\t$i\t"
echo $(git shortlog -n --no-merges --format='%ad %s' $_since $_until | grep " $i " | wc -l)
done | awk '{
count[$1] = $2
total += $2
}
END{
for (month in count) {
s="|";
percent = ((count[month] / total) * 100) / 1.25;
for (i = 1; i <= percent; ++i) {
s=s"█"
}
printf( "\t%s\t%-0s\t%s\n", month, count[month], s );
}
}' | LC_TIME="en_EN.UTF-8" sort -M
}
function commitsByWeekday() {
option_picked "Git commits by weekday:"
echo -e "\tday\tsum"
for i in Mon Tue Wed Thu Fri Sat Sun
do
echo -en "\t$i\t"
echo $(git shortlog -n --no-merges --format='%ad %s' $_since $_until | grep "$i " | wc -l)
done | awk '{
}
NR == FNR {
count[$1] = $2;
total += $2;
next
}
END{
for (day in count) {
s="|";
percent = ((count[day] / total) * 100) / 1.25;
for (i = 1; i <= percent; ++i) {
s=s"█"
}
printf( "\t%s\t%-0s\t%s\n", day, count[day], s );
}
}' | sort -k 2 -n -r
}
function commitsByHour() {
local author="${1:-}"
local _author=''
if [ -z "$author" ]; then
option_picked "Git commits by hour:"
else
option_picked "Git commits by hour for author '$author':"
_author="--author=$author"
fi
echo -e "\thour\tsum"
for i in $(seq -w 0 23)
do
echo -ne "\t$i\t"
echo "$(git shortlog -n --no-merges --format='%ad %s' $_author $_since $_until | grep ' '$i: | wc -l)"
done | awk '{
count[$1] = $2
total += $2
}
END{
for (hour in count) {
s="|";
percent = ((count[hour] / total) * 100) / 1.25;
for (i = 1; i <= percent; ++i) {
s=s"█"
}
printf( "\t%s\t%-0s\t%s\n", hour, count[hour], s );
}
}' | sort
}
function commitsPerDay() {
option_picked "Git commits per date:";
git log --use-mailmap --no-merges $_since $_until --date=short --format='%ad' $_pathspec | sort | uniq -c
}
function commitsPerAuthor() {
option_picked "Git commits per author:"
git shortlog $_since $_until --no-merges -n -s | sort -nr | LC_ALL=C awk '
{ args[NR] = $0; sum += $0 }
END {
for (i = 1; i <= NR; ++i) {
printf "%s,%2.1f%%\n", args[i], 100 * args[i] / sum
}
}' | column -t -s,
}
function myDailyStats() {
option_picked "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 log --use-mailmap --author="$(git config user.name)" --no-merges --since=$(date "+%Y-%m-%dT00:00:00") --until=$(date "+%Y-%m-%dT23:59:59") --reverse | grep commit | wc -l) "commits"
}
function contributors() {
option_picked "All contributors (sorted by name):"
git log --use-mailmap --no-merges $_since $_until --format='%aN' $_pathspec | sort -u | cat -n
}
function branchTree() {
option_picked "Branching tree view:"
git log --use-mailmap --graph --abbrev-commit $_since $_until --decorate --format=format:'--+ Commit: %h %n | Date: %aD (%ar) %n'' | Message: %s %d %n'' + Author: %aN %n' --all | head -n $((_limit*5))
}
function branchesByDate() {
option_picked "All branches (sorted by most recent commit):"
git for-each-ref --sort=committerdate refs/heads/ --format='[%(authordate:relative)] %(authorname) %(refname:short)' | cat -n
}
function changelogs() {
local author="${1:-}"
local _author
if [ -z "$author" ]; then
option_picked "Git changelogs:"
_author="--author=**"
else
option_picked "Git changelogs for author '$author':"
_author="--author=$author"
fi
NEXT=$(date +%F)
git log --use-mailmap --no-merges --format="%cd" --date=short "$_author" $_since $_until $_pathspec | sort -u -r | head -n $_limit | while read DATE ; do
echo
echo "[$DATE]"
GIT_PAGER=cat git log --use-mailmap --no-merges --format=" * %s (%aN)" "$_author" --since=$DATE --until=$NEXT
NEXT=$DATE
done
}
# Check if we are currently in a git repo.
git rev-parse --is-inside-work-tree > /dev/null
if [ $# -eq 1 ]
then
case $1 in
"suggestReviewers")
suggestReviewers
;;
"detailedGitStats")
detailedGitStats
;;
"branchTree")
branchTree
;;
"commitsPerDay")
commitsPerDay
;;
"commitsPerAuthor")
commitsPerAuthor
;;
"myDailyStats")
myDailyStats
;;
"contributors")
contributors
;;
"branchesByDate")
branchesByDate
;;
"changelogs")
changelogs
;;
"changelogsByAuthor")
author="${_GIT_AUTHOR:-}"
while [ -z "$author" ]; do read -p "Which author? " author; done
changelogs "$author"
;;
"commitsByWeekday")
commitsByWeekday
;;
"commitsByHour")
commitsByHour
;;
"commitsByAuthorByHour")
author="${_GIT_AUTHOR:-}"
while [ -z "$author" ]; do read -p "Which author? " author; done
commitsByHour "$author"
;;
"commitsByMonth")
commitsByMonth
;;
*)
echo "Invalid argument. Possible arguments: suggestReviewers, detailedGitStats, commitsPerDay, commitsByMonth, commitsByWeekday, commitsByHour, commitsByAuthorByHour, commitsPerAuthor, myDailyStats, contributors, branchTree, branchesByDate, changelogs, changelogsByAuthor"
exit 1
;;
esac
exit 0;
fi
if [ $# -gt 1 ]
then
echo "Usage: git quick-stats <optional-command-to-execute-directly>";
exit 1;
fi
clear
show_menu
while [ opt != '' ]
do
if [[ $opt = "" ]]; then
exit;
else
clear
case $opt in
1)
detailedGitStats
show_menu
;;
2)
changelogs
show_menu
;;
3)
author=''
while [ -z "$author" ]; do read -p "Which author? " author; done
changelogs "$author"
show_menu
;;
4)
myDailyStats
show_menu
;;
5)
branchTree
show_menu
;;
6)
branchesByDate
show_menu
;;
7)
contributors
show_menu
;;
8)
commitsPerAuthor
show_menu
;;
9)
commitsPerDay
show_menu
;;
10)
commitsByMonth
show_menu
;;
11)
commitsByWeekday
show_menu
;;
12)
commitsByHour
show_menu
;;
13)
author=''
while [ -z "$author" ]; do read -p "Which author? " author; done
commitsByHour "$author"
show_menu
;;
14)
suggestReviewers
show_menu
;;
q)
exit
;;
\n)
exit
;;
*)
clear
option_picked "Pick an option from the menu"
show_menu
;;
esac
fi
done