mirror of
https://github.com/git-quick-stats/git-quick-stats.git
synced 2025-12-16 12:00:12 +01:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
228e67f879 | ||
|
|
891e2277ad | ||
|
|
cc87b3046f | ||
|
|
7aea224e21 | ||
|
|
c37492bb4c | ||
|
|
c258e34afe | ||
|
|
9e18cf35d4 | ||
|
|
24ae67ae57 | ||
|
|
ed0e3cbb6b | ||
|
|
46a771138e | ||
|
|
9f54b87ed5 | ||
|
|
246076f5f6 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -2,3 +2,4 @@
|
||||
|
||||
github: [arzzen]
|
||||
open_collective: git-quick-stats
|
||||
custom: ['https://lukasmestan.com/thanks/']
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Lukáš Mešťan
|
||||
Copyright (c) 2020 Lukáš Mešťan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
160
git-quick-stats
160
git-quick-stats
@@ -8,24 +8,42 @@ set -o nounset
|
||||
set -o errexit
|
||||
|
||||
# Beginning git log date. Respects all git datetime formats
|
||||
# If $_GIT_SINCE is never set, choose epoch time as that is
|
||||
# as far back as git will allow you to go
|
||||
_since=${_GIT_SINCE:-}
|
||||
[[ -n "${_since}" ]] && _since="--since=$_since"
|
||||
if [[ -n "${_since}" ]]; then
|
||||
_since="--since=$_since"
|
||||
else
|
||||
_since="--since=1970-01-01"
|
||||
fi
|
||||
|
||||
# End of git log date. Respects all git datetime formats
|
||||
# If $_GIT_UNTIL is never set, choose the latest system
|
||||
# time from the user's current environment
|
||||
_until=${_GIT_UNTIL:-}
|
||||
[[ -n "${_until}" ]] && _until="--until=$_until"
|
||||
if [[ -n "${_until}" ]]; then
|
||||
_until="--until=$_until"
|
||||
else
|
||||
_until="--until=$(date)"
|
||||
fi
|
||||
|
||||
# Set files or directories to be excluded in stats
|
||||
# If $_GIT_PATHSPEC is not set, shift over the option completely
|
||||
_pathspec=${_GIT_PATHSPEC:-}
|
||||
[[ -n "${_pathspec}" ]] && _pathspec="-- $_pathspec"
|
||||
if [[ -n "${_pathspec}" ]]; then
|
||||
_pathspec="-- $_pathspec"
|
||||
else
|
||||
_pathspec="--"
|
||||
fi
|
||||
|
||||
# Set merge commit view strategy. Default is to show no merge commits
|
||||
# Exclusive shows only merge commits
|
||||
# Enable shows regular commits together with normal commits
|
||||
_merges=${_GIT_MERGE_VIEW:-}
|
||||
if [[ "${_merges,,}" == "exclusive" ]]; then
|
||||
_merges=$(echo "$_merges" | awk '{print tolower($0)}')
|
||||
if [[ "${_merges}" == "exclusive" ]]; then
|
||||
_merges="--merges"
|
||||
elif [[ "${_merges,,}" == "enable" ]]; then
|
||||
elif [[ "${_merges}" == "enable" ]]; then
|
||||
_merges=""
|
||||
else
|
||||
_merges="--no-merges"
|
||||
@@ -33,7 +51,7 @@ fi
|
||||
|
||||
# Limit git log output
|
||||
_limit=${_GIT_LIMIT:-}
|
||||
if [[ -n "${_limit}" ]]; then
|
||||
if [[ -n "${_limit}" ]]; then
|
||||
_limit=$_limit
|
||||
else
|
||||
_limit=10
|
||||
@@ -52,7 +70,7 @@ function checkUtils() {
|
||||
local -r msg="not found. Please make sure this is installed and in PATH."
|
||||
declare -ar utils=("awk" "basename" "cat" "column" "echo" "git" "grep" "head"
|
||||
"seq" "sort" "tput" "tr" "uniq" "wc")
|
||||
|
||||
|
||||
for u in "${utils[@]}"
|
||||
do
|
||||
command -v "$u" >/dev/null 2>&1 || { echo >&2 "$u ${msg}"; exit 1; }
|
||||
@@ -64,7 +82,7 @@ function checkUtils() {
|
||||
# ARGS: None
|
||||
# OUTS: None
|
||||
################################################################################
|
||||
function usage() {
|
||||
function usage() {
|
||||
local -r program=$(basename "$0")
|
||||
|
||||
echo "
|
||||
@@ -165,7 +183,7 @@ function showMenu() {
|
||||
help_txt="${normal}${cyan}" && readonly help_txt
|
||||
exit_txt="${bold}${cyan}" && readonly exit_txt
|
||||
fi
|
||||
|
||||
|
||||
echo -e "\n${titles} Generate:${normal}"
|
||||
echo -e "${nums} 1)${text} Contribution stats (by author)"
|
||||
echo -e "${nums} 2)${text} Contribution stats (by author) on a specific branch"
|
||||
@@ -200,7 +218,7 @@ function optionPicked() {
|
||||
local -r red=$(tput setaf 1)
|
||||
local -r reset=$(tput sgr0)
|
||||
local msg=${*:-"${reset}Error: No message passed"}
|
||||
|
||||
|
||||
echo -e "${bold}${red}${msg}${reset}\n"
|
||||
}
|
||||
|
||||
@@ -215,7 +233,7 @@ function detailedGitStats() {
|
||||
local is_branch_existing=false
|
||||
local branch="${1:-}"
|
||||
local _branch=""
|
||||
|
||||
|
||||
# Check if requesting for a specific branch
|
||||
if [[ -n "${branch}" ]]; then
|
||||
# Check if branch exist
|
||||
@@ -239,32 +257,32 @@ function detailedGitStats() {
|
||||
|
||||
git -c log.showSignature=false log ${_branch} --use-mailmap $_merges --numstat \
|
||||
--pretty="format:commit %H%nAuthor: %aN <%aE>%nDate: %ad%n%n%w(0,4,4)%B%n" \
|
||||
$_since $_until $_pathspec | LC_ALL=C awk '
|
||||
"$_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], \
|
||||
printf "\t insertions: %d\t(%.0f%%)\n", more[author], \
|
||||
(more[author] / more["total"] * 100)
|
||||
}
|
||||
|
||||
if(less["total"] > 0) {
|
||||
printf "\t deletions: %d (%.0f%%)\n", less[author], \
|
||||
printf "\t deletions: %d\t(%.0f%%)\n", less[author], \
|
||||
(less[author] / less["total"] * 100)
|
||||
}
|
||||
|
||||
if(file["total"] > 0) {
|
||||
printf "\t files: %d (%.0f%%)\n", file[author], \
|
||||
printf "\t files: %d\t(%.0f%%)\n", file[author], \
|
||||
(file[author] / file["total"] * 100)
|
||||
}
|
||||
|
||||
if(commits["total"] > 0) {
|
||||
printf "\t commits: %d (%.0f%%)\n", commits[author], \
|
||||
printf "\t commits: %d\t(%.0f%%)\n", commits[author], \
|
||||
(commits[author] / commits["total"] * 100)
|
||||
}
|
||||
|
||||
if (first[author] != "") {
|
||||
printf "\t lines changed: %s\n", more[author] + less[author]
|
||||
printf "\t lines changed: %d\t(%.0f%%)\n", more[author] + less[author], ((more[author] + less[author])/ (more["total"]+ less["total"]) * 100 )
|
||||
printf "\t first commit: %s\n", first[author]
|
||||
printf "\t last commit: %s\n", last[author]
|
||||
}
|
||||
@@ -288,8 +306,8 @@ function detailedGitStats() {
|
||||
/^[0-9]/ {
|
||||
more[author] += $1
|
||||
less[author] += $2
|
||||
file[author] += 1
|
||||
|
||||
file[author] += 1
|
||||
more["total"] += $1
|
||||
less["total"] += $2
|
||||
file["total"] += 1
|
||||
@@ -312,8 +330,8 @@ function detailedGitStats() {
|
||||
################################################################################
|
||||
function suggestReviewers() {
|
||||
optionPicked "Suggested code reviewers (based on git history):"
|
||||
git -c log.showSignature=false log --use-mailmap $_merges $_since $_until \
|
||||
--pretty=%aN $_pathspec | head -n 100 | sort | uniq -c | sort -nr | LC_ALL=C awk '
|
||||
git -c log.showSignature=false log --use-mailmap $_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) {
|
||||
@@ -328,13 +346,13 @@ function suggestReviewers() {
|
||||
# OUTS: A JSON formatted file
|
||||
################################################################################
|
||||
function jsonOutput() {
|
||||
optionPicked "Output log saved to file at: ${json_path:?}/output.json"
|
||||
git -c log.showSignature=false log --use-mailmap $_merges $_since $_until \
|
||||
optionPicked "Output log saved to file at: ${json_path}/output.json"
|
||||
git -c log.showSignature=false log --use-mailmap $_merges "$_since" "$_until" \
|
||||
--pretty=format:'{%n "commit": "%H",%n "abbreviated_commit": "%h",%n "tree": "%T",%n "abbreviated_tree": "%t",%n "parent": "%P",%n "abbreviated_parent": "%p",%n "refs": "%D",%n "encoding": "%e",%n "subject": "%s",%n "sanitized_subject_line": "%f",%n "body": "%b",%n "commit_notes": "%N",%n "author": {%n "name": "%aN",%n "email": "%aE",%n "date": "%aD"%n },%n "commiter": {%n "name": "%cN",%n "email": "%cE",%n "date": "%cD"%n }%n},' \
|
||||
| sed "$ s/,$//" \
|
||||
| sed ':a;N;$!ba;s/\r\n\([^{]\)/\\n\1/g' \
|
||||
| awk 'BEGIN { print("[") } { print($0) } END { print("]") }' \
|
||||
> "${json_path:?}"/output.json
|
||||
> "${json_path}/output.json"
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@@ -349,12 +367,12 @@ function commitsByMonth() {
|
||||
do
|
||||
echo -en "\t$i\t"
|
||||
git -c log.showSignature=false shortlog -n $_merges --format='%ad %s' \
|
||||
$_since $_until | grep " $i " | wc -l
|
||||
done | awk '{
|
||||
count[$1] = $2
|
||||
total += $2
|
||||
}
|
||||
END{
|
||||
"$_since" "$_until" | grep " $i " | wc -l
|
||||
done | awk '{
|
||||
count[$1] = $2
|
||||
total += $2
|
||||
}
|
||||
END{
|
||||
for (month in count) {
|
||||
s="|";
|
||||
if (total > 0) {
|
||||
@@ -376,21 +394,21 @@ function commitsByMonth() {
|
||||
function commitsByWeekday() {
|
||||
optionPicked "Git commits by weekday:"
|
||||
echo -e "\tday\tsum"
|
||||
local counter=1
|
||||
for i in Mon Tue Wed Thu Fri Sat Sun
|
||||
do
|
||||
echo -en "\t$i\t"
|
||||
echo -en "\t$counter\t$i\t"
|
||||
git -c log.showSignature=false shortlog -n $_merges --format='%ad %s' \
|
||||
$_since $_until | grep "$i " | wc -l
|
||||
"$_since" "$_until" | grep "$i " | wc -l
|
||||
counter=$((counter+1))
|
||||
done | awk '{
|
||||
|
||||
}
|
||||
NR == FNR {
|
||||
count[$1] = $2;
|
||||
total += $2;
|
||||
next
|
||||
NR == FNR {
|
||||
count[$1" "$2] = $3;
|
||||
total += $3;
|
||||
next
|
||||
}
|
||||
END{
|
||||
|
||||
END{
|
||||
for (day in count) {
|
||||
s="|";
|
||||
if (total > 0) {
|
||||
@@ -398,10 +416,10 @@ function commitsByWeekday() {
|
||||
for (i = 1; i <= percent; ++i) {
|
||||
s=s"█"
|
||||
}
|
||||
printf( "\t%s\t%-0s\t%s\n", day, count[day], s );
|
||||
printf("\t%s\t%s\t%-0s\t%s\n", substr(day,0,1), substr(day,3,5), count[day], s);
|
||||
}
|
||||
}
|
||||
}' | sort -k 2 -n -r
|
||||
}' | sort -k 1 -n | awk '{$1=""}1' | awk '{$1=$1}1' | awk '{printf("\t%s\t%s\t%s\n", $1, $2, $3)}'
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@@ -425,12 +443,12 @@ function commitsByHour() {
|
||||
do
|
||||
echo -ne "\t$i\t"
|
||||
git -c log.showSignature=false shortlog -n $_merges --format='%ad %s' \
|
||||
"${_author}" $_since $_until | grep ' '$i: | wc -l
|
||||
done | awk '{
|
||||
count[$1] = $2
|
||||
total += $2
|
||||
}
|
||||
END{
|
||||
"${_author}" "$_since" "$_until" | grep ' '$i: | wc -l
|
||||
done | awk '{
|
||||
count[$1] = $2
|
||||
total += $2
|
||||
}
|
||||
END{
|
||||
for (hour in count) {
|
||||
s="|";
|
||||
if (total > 0) {
|
||||
@@ -452,8 +470,8 @@ function commitsByHour() {
|
||||
################################################################################
|
||||
function commitsPerDay() {
|
||||
optionPicked "Git commits per date:";
|
||||
git -c log.showSignature=false log --use-mailmap $_merges $_since $_until \
|
||||
--date=short --format='%ad' $_pathspec | sort | uniq -c
|
||||
git -c log.showSignature=false log --use-mailmap $_merges "$_since" "$_until" \
|
||||
--date=short --format='%ad' "$_pathspec" | sort | uniq -c
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@@ -465,9 +483,9 @@ function commitsPerDay() {
|
||||
function commitsPerAuthor() {
|
||||
optionPicked "Git commits per author:"
|
||||
local authorCommits=$(git -c log.showSignature=false log --use-mailmap $_merges \
|
||||
$_since $_until | grep -i Author: | cut -c9-)
|
||||
"$_since" "$_until" | grep -i Author: | cut -c9-)
|
||||
local coAuthorCommits=$(git -c log.showSignature=false log --use-mailmap $_merges \
|
||||
$_since $_until | grep -i Co-Authored-by: | cut -c21-)
|
||||
"$_since" "$_until" | grep -i Co-Authored-by: | cut -c21-)
|
||||
|
||||
if [[ -z "${coAuthorCommits}" ]]
|
||||
then
|
||||
@@ -517,8 +535,8 @@ function myDailyStats() {
|
||||
################################################################################
|
||||
function contributors() {
|
||||
optionPicked "All contributors (sorted by name):"
|
||||
git -c log.showSignature=false log --use-mailmap $_merges $_since $_until \
|
||||
--format='%aN' $_pathspec | sort -u | cat -n
|
||||
git -c log.showSignature=false log --use-mailmap $_merges "$_since" "$_until" \
|
||||
--format='%aN' "$_pathspec" | sort -u | cat -n
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@@ -529,7 +547,7 @@ function contributors() {
|
||||
function branchTree() {
|
||||
optionPicked "Branching tree view:"
|
||||
git -c log.showSignature=false log --use-mailmap --graph --abbrev-commit \
|
||||
$_since $_until --decorate \
|
||||
"$_since" "$_until" --decorate \
|
||||
--format=format:'--+ Commit: %h %n | Date: %aD (%ar) %n'' | Message: %s %d %n'' + Author: %aN %n' \
|
||||
--all | head -n $((_limit*5))
|
||||
}
|
||||
@@ -567,7 +585,7 @@ function changelogs() {
|
||||
--use-mailmap \
|
||||
$_merges \
|
||||
--format="%cd" \
|
||||
--date=short "${_author}" $_since $_until $_pathspec \
|
||||
--date=short "${_author}" "$_since" "$_until" "$_pathspec" \
|
||||
| sort -u -r | head -n $_limit \
|
||||
| while read DATE; do
|
||||
echo -e "\n[$DATE]"
|
||||
@@ -595,7 +613,7 @@ if [[ "$#" -eq 1 ]]; then
|
||||
-T|--detailed-git-stats) detailedGitStats;;
|
||||
-R|--git-stats-by-branch)
|
||||
branch=""
|
||||
while [[ -z "${branch}" ]]; do
|
||||
while [[ -z "${branch}" ]]; do
|
||||
read -r -p "Which branch? " branch
|
||||
done
|
||||
detailedGitStats "${branch}";;
|
||||
@@ -608,7 +626,7 @@ if [[ "$#" -eq 1 ]]; then
|
||||
-c|--changelogs) changelogs;;
|
||||
-L|--changelogs-by-author)
|
||||
author="${_GIT_AUTHOR:-}"
|
||||
while [[ -z "${author}" ]]; do
|
||||
while [[ -z "${author}" ]]; do
|
||||
read -r -p "Which author? " author
|
||||
done
|
||||
changelogs "${author}";;
|
||||
@@ -616,15 +634,23 @@ if [[ "$#" -eq 1 ]]; then
|
||||
-o|--commits-by-hour) commitsByHour;;
|
||||
-A|--commits-by-author-by-hour)
|
||||
author="${_GIT_AUTHOR:-}"
|
||||
while [[ -z "${author}" ]]; do
|
||||
while [[ -z "${author}" ]]; do
|
||||
read -r -p "Which author? " author
|
||||
done
|
||||
commitsByHour "${author}";;
|
||||
-m|--commits-by-month) commitsByMonth;;
|
||||
-j|--json-output)
|
||||
-j|--json-output)
|
||||
json_path=""
|
||||
while [[ -z "${json_path}" ]]; do
|
||||
read -r -p "Path to save JSON file: " json_path
|
||||
echo "NOTE: This feature is in beta!"
|
||||
echo "The file name will be saved as \"output.json\"."
|
||||
echo "The full path must be provided."
|
||||
echo "Variables or shorthands such as ~ are not valid."
|
||||
echo "You do not need the final slash at the end of a directory path."
|
||||
echo "You must have write permission to the folder you are trying to save this to."
|
||||
echo "This feature only works interactively and cannot be combined with other options."
|
||||
echo -e "Example of a valid path: /home/$(whoami)\n"
|
||||
read -r -p "Please provide the full path to directory to save JSON file: " json_path
|
||||
if [[ ! -w "${json_path}" ]]; then
|
||||
echo "Invalid path or permission denied to write to given area."
|
||||
json_path=""
|
||||
@@ -647,20 +673,28 @@ while [[ "${opt}" != "" ]]; do
|
||||
case "${opt}" in
|
||||
1) detailedGitStats; showMenu;;
|
||||
2) branch=""
|
||||
while [[ -z "${branch}" ]]; do
|
||||
while [[ -z "${branch}" ]]; do
|
||||
read -r -p "Which branch? " branch
|
||||
done
|
||||
detailedGitStats "${branch}"; showMenu;;
|
||||
3) changelogs; showMenu;;
|
||||
4) author=""
|
||||
while [[ -z "${author}" ]]; do
|
||||
while [[ -z "${author}" ]]; do
|
||||
read -r -p "Which author? " author
|
||||
done
|
||||
changelogs "${author}"; showMenu;;
|
||||
5) myDailyStats; showMenu;;
|
||||
6) json_path=""
|
||||
while [[ -z "${json_path}" ]]; do
|
||||
read -r -p "Path to save JSON file: " json_path
|
||||
echo "NOTE: This feature is in beta!"
|
||||
echo "The file name will be saved as \"output.json\"."
|
||||
echo "The full path must be provided."
|
||||
echo "Variables, subshell commands, or shorthands such as ~ may not be valid."
|
||||
echo "You do not need the final slash at the end of a directory path."
|
||||
echo "You must have write permission to the folder you are trying to save this to."
|
||||
echo "This feature only works interactively and cannot be combined with other options."
|
||||
echo -e "Example of a valid path: /home/$(whoami)\n"
|
||||
read -r -p "Please provide the full path to directory to save JSON file: " json_path
|
||||
if [[ ! -w "${json_path}" ]]; then
|
||||
echo "Invalid path or permission denied to write to given area."
|
||||
json_path=""
|
||||
@@ -676,7 +710,7 @@ while [[ "${opt}" != "" ]]; do
|
||||
13) commitsByWeekday; showMenu;;
|
||||
14) commitsByHour; showMenu;;
|
||||
15) author=""
|
||||
while [[ -z "${author}" ]]; do
|
||||
while [[ -z "${author}" ]]; do
|
||||
read -r -p "Which author? " author
|
||||
done
|
||||
commitsByHour "${author}"; showMenu;;
|
||||
|
||||
Reference in New Issue
Block a user