Skip to content

Instantly share code, notes, and snippets.

@n1kk
Last active June 2, 2022 21:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save n1kk/729403d64e0dc9f1ac00c5d7df6b32ee to your computer and use it in GitHub Desktop.
Save n1kk/729403d64e0dc9f1ac00c5d7df6b32ee to your computer and use it in GitHub Desktop.
script to print out git diff line changes per specific files category

This script reads git diff and filters lines based on file category. This allows to see line changes for only source files or test or doccumentation.

If you add this script as git alias you'll be able to call it like git count source

$ ./git-diff-count-lines.sh count source
in 5 files: added: 13, removed: 142, total: 155, diff: -129
$ ./git-diff-count-lines.sh count source --nums
5 13 142 155 -129
$ git count source configs
in 11 files: added: 202, removed: 304, total: 506, diff: -102
$ git count list
source:     5    +13   -142   =155   :129
tests:      0     +0     -0     =0     :0
code:       5    +13   -142   =155   :129
configs:    6   +189   -162   =351    :27
assets:     0     +0     -0     =0     :0
docs:       0     +0     -0     =0     :0

Categories can be configured in count-lines.ini, to include line write awk regex for it, to exclude prepend ! to regex.

[category]
include = /(dir1|dir2)\/.*\.(ext1|ext2)/
exclude = !/^unwanted.file/
[code]
code = /(src|styles)\/.*\.(jsx?|tsx?|[sp]?css|vue|html)/
# exclude
specs = !/\.specs?\./
tests = !/\s*tests\//
[tests]
specs = /\.specs?\./
tests = /^tests\//
[source]
code = /(src|styles)\/.*\.(jsx?|tsx?|[sp]?css|vue|html)/
specs = /\.specs?\./
tests = /\s*tests\//
[configs]
dot-files = /^\..*|\/\..*/
dot-config = /.*\.config\..*/
config-exts = /\.(json|yml|cgf|rc|toml|ini|properties)/
build = /^build\//
# exclude
package-lock = !/package-lock\.json|yarn\.lock/
[assets]
asset-folder = /^assets\/.*/
asset-exts = /\.(svg|ico|jpg|jpeg|png|webp|webm|tiff|woff|otf|ttf)/
[docs]
docs-folder = /^docs\/.*/
md_files = /\.md/
#!/usr/bin/env bash
function log() {
local content
if [[ $# != 0 ]] || [[ -t 0 ]]; then
content="${@}"
else
content="$( < /dev/stdin )"
fi
# direct to stdout
echo "$content" >&2
}
function count-lines() {
local config_text=$(gawk '{
FS="=";
catMatch = match($0, /^\[(.*)\]/, _ca)
if (catMatch != 0) {
cat = _ca[1]
}
valMatch = match($0, /^\s*([a-zA-Z0-9_\-]+)\s*=\s*(!?)\/(.*)\/\s*$/, _va)
if (valMatch != 0) {
printf("%s %s %s %s\n", cat, _va[1], _va[3], (_va[2]=="!" ? "-" : "+"));
}
}' "${BASH_SOURCE%/*}/count-lines.ini")
declare -A unique_cats
while read -r category other; do
# log "cat ${category}"
unique_cats[$category]=1
done <<< "$config_text"
local categories=( "${!unique_cats[@]}" )
local include_patterns=()
local exclude_patterns=()
local numbers_only=0
local list_all=0
local verbose=0
local sub_args=""
local filter_categories=()
while [[ $# -gt 0 ]]; do
opt="$1";
shift; #expose next argument
case "$opt" in
"--incl" ) include_patterns+=("($1)"); shift;;
"--excl" ) exclude_patterns+=("($1)"); shift;;
"--nums" ) numbers_only=1;;
"--verb" ) verbose=1;;
*)
if [[ " ${categories[@]} " =~ " ${opt} " ]]; then
filter_categories+=( "$opt" )
else
log "Unknown category '$opt'"
return 1
fi
;;
esac
done
if [ ${#filter_categories[@]} -eq 0 ]; then
list_all=1
fi
local changes
if [[ -t 0 ]]; then # terminal
changes=$(git diff --numstat)
else # pipe
changes="$( < /dev/stdin )"
fi
if [[ ${list_all} == 1 ]]; then
for cat in "${categories[@]}"; do
local args=""
[[ $verbose = 1 ]] && args="$args --verb";
local nums="$(echo "$changes" | count-lines --nums "$cat" $args)"
[[ $? != 0 ]] && return $?;
echo "$cat: $nums" | awk '{
printf("%-8s %4d %6s %6s %6s %6s\n", $1, $2, "+"$3, "-"$4, "="$5, ":"($6>0?$6:-$6))
}'
done
return
fi
while read -r category section pattern action; do
if [[ " ${filter_categories[@]} " =~ " ${category} " ]]; then
# log "$category $pattern $action"
[[ "$action" == "+" ]] && include_patterns+=("($pattern)");
[[ "$action" == "-" ]] && exclude_patterns+=("($pattern)");
fi
done <<< "$config_text"
local match_pattern="$( ( IFS=$'|'; echo "${include_patterns[*]}" ))"
local unmatch_pattern="$( ( IFS=$'|'; echo "${exclude_patterns[*]}" ))"
if [[ ! -z "${unmatch_pattern// }" ]]; then
changes=$(echo "$changes" | awk "\$3 !~ /$unmatch_pattern/")
fi
if [[ ! -z "${match_pattern// }" ]]; then
changes=$(echo "$changes" | awk "\$3 ~ /$match_pattern/")
else
changes=""
fi
local awk_line='{
if (length($0) != 0) files += 1;
add += $1;
rem += $2
}'
local awk_printf_str
if [[ ${numbers_only} == 1 ]]; then
awk_printf_str="%d %d %d %d %d\n"
else
awk_printf_str="in %d files: added: %d, removed: %d, total: %d, diff: %d\n"
fi
local awk_end="END {\
tot = add + rem; dif = add - rem;\
printf \"$awk_printf_str\", files, add, rem, tot, dif\
}"
local awk_cmd="$awk_line $awk_end"
if [[ ${verbose} == 1 ]]; then
echo "$changes" | awk '{print " " $0}' | log
fi
echo "$changes" | awk "$awk_cmd"
}
function main() {
local cmd="$1";
shift
case "$cmd" in
"count" ) count-lines $@;;
"list-counts" ) count-lines list;;
*) echo >&2 "Unknown command: $cmd"; exit 1;;
esac
}
main $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment