Skip to content

Instantly share code, notes, and snippets.

@kvalv
Created May 29, 2026 08:16
Show Gist options
  • Select an option

  • Save kvalv/21fca5e4607cafd59a921ef5e632f04c to your computer and use it in GitHub Desktop.

Select an option

Save kvalv/21fca5e4607cafd59a921ef5e632f04c to your computer and use it in GitHub Desktop.
git-contributors
#!/usr/bin/env bash
#
# git-contributors [options] <path>
#
# Show the main contributors for a given path, ranked by commits, with
# lines added/removed, the date range of their activity, and a per-author
# sparkline of commit activity over time (one cell per month).
#
# Defaults to the last 12 months. Pass --since "" to include all history.
set -euo pipefail
usage() {
cat <<'EOF'
git-contributors — who are the active contributors for a path?
Usage:
git-contributors [options] <path>
Options:
-s, --since <when> Window to consider (default: "12 months ago").
Use --since "" for all history.
-n, --top <N> Show only the top N contributors (default: all).
-e, --email Group by email instead of name.
-h, --help Show this help.
Examples:
git-contributors . # this directory, last 12 months
git-contributors internal/activities # a subdirectory
git-contributors --top 10 . # top 10 contributors
git-contributors --since "3 months ago" . # narrower window
git-contributors --since "" services/pipeline # all history
git-contributors --email . # group by email
The ACTIVITY column is a sparkline of commits per month (oldest -> newest)
across the selected window, so you can see who is ramping up or trailing off.
EOF
}
# No arguments at all -> show help.
if [[ $# -eq 0 ]]; then
usage
exit 0
fi
since="12 months ago"
top=0
group_by="name"
path=""
while [[ $# -gt 0 ]]; do
case "$1" in
-s|--since) since="$2"; shift 2 ;;
-n|--top) top="$2"; shift 2 ;;
-e|--email) group_by="email"; shift ;;
-h|--help) usage; exit 0 ;;
-*) echo "unknown option: $1" >&2; usage >&2; exit 1 ;;
*) path="$1"; shift ;;
esac
done
if [[ -z "$path" ]]; then
echo "error: no path given" >&2
echo >&2
usage >&2
exit 1
fi
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "error: not inside a git repository" >&2
exit 1
fi
since_args=()
[[ -n "$since" ]] && since_args=(--since="$since")
if [[ "$group_by" == "email" ]]; then
fmt="%ae"
else
fmt="%an"
fi
total_commits=$(git log "${since_args[@]}" --no-merges --oneline -- "$path" | wc -l | tr -d ' ')
echo "Contributors for: $path"
if [[ -n "$since" ]]; then
echo "Window: since $since"
else
echo "Window: all history"
fi
echo "Total commits: $total_commits"
echo
if [[ "$total_commits" == "0" ]]; then
echo "No commits found."
exit 0
fi
# Emit per-commit author + month, plus numstat lines. awk aggregates totals
# and a per-author per-month commit count, then renders a sparkline spanning
# the full month range present in the data.
git log "${since_args[@]}" --no-merges \
--pretty=format:"C%x09${fmt}%x09%ad" --date=format:'%Y-%m' --numstat -- "$path" \
| awk -F'\t' '
function spark(count, levels, i) {
if (count <= 0) return " "
# 8 levels of block characters
split("▁▂▃▄▅▆▇█", levels, "")
i = count
if (i > 8) i = 8
return levels[i]
}
/^C\t/ {
author = $2
month = $3
commits[author]++
cell[author SUBSEP month]++
months[month] = 1
if (!(author in first) || month < first[author]) first[author] = month
if (!(author in last) || month > last[author]) last[author] = month
next
}
NF >= 3 && $1 ~ /^[0-9]+$/ {
added[author] += $1
removed[author] += $2
}
END {
# Ordered list of all months seen.
n = 0
for (m in months) order[++n] = m
# insertion sort (small n)
for (i = 2; i <= n; i++) {
key = order[i]; j = i - 1
while (j >= 1 && order[j] > key) { order[j+1] = order[j]; j-- }
order[j+1] = key
}
printf "%-26s %7s %8s %8s %-*s %s\n", "AUTHOR", "COMMITS", "+LINES", "-LINES", (n>9?n:9), "ACTIVITY", "RANGE"
for (a in commits) {
line = ""
for (i = 1; i <= n; i++) line = line spark(cell[a SUBSEP order[i]])
printf "%d\t%-26s %7d %8d %8d %-*s %s -> %s\n", \
commits[a], a, commits[a], added[a], removed[a], (n>9?n:9), line, first[a], last[a]
}
}
' \
| { read -r header; echo "$header"; sort -rn -k1,1 | cut -f2-; } \
| { [[ "$top" -gt 0 ]] && { read -r header; echo "$header"; head -n "$top"; } || cat; }
if [[ -n "$since" ]]; then
echo
echo "(ACTIVITY: one cell per month, oldest -> newest; taller = more commits)"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment