Skip to content

Instantly share code, notes, and snippets.

@domix
Last active August 7, 2026 20:20
Show Gist options
  • Select an option

  • Save domix/9219fd108a213a9c83138704b6b0a0e5 to your computer and use it in GitHub Desktop.

Select an option

Save domix/9219fd108a213a9c83138704b6b0a0e5 to your computer and use it in GitHub Desktop.
GitHub metrics
#!/usr/bin/env bash
# GitHub usage metrics for a date range, via gh Search API.
# Usage: ./gh-metrics.sh <since> <until> [user]
# dates are YYYY-MM-DD (inclusive). user defaults to the authenticated account.
# Example: ./gh-metrics.sh 2026-01-01 2026-06-30
set -euo pipefail
since="${1:?usage: gh-metrics.sh <since:YYYY-MM-DD> <until:YYYY-MM-DD> [user]}"
until="${2:?usage: gh-metrics.sh <since:YYYY-MM-DD> <until:YYYY-MM-DD> [user]}"
user="${3:-$(gh api user -q .login)}"
date_re='^[0-9]{4}-[0-9]{2}-[0-9]{2}$'
[[ "$since" =~ $date_re && "$until" =~ $date_re ]] || { echo "error: dates must be YYYY-MM-DD" >&2; exit 1; }
[[ "$since" > "$until" ]] && { echo "error: since ($since) is after until ($until)" >&2; exit 1; }
# ponytail: search/commits total_count is authoritative enough for personal metrics;
# it excludes private repos the token can't see. Add per-repo walk if you need those.
# metric(): separate `local n` from the assignment so a gh api failure aborts
# under set -e instead of printing a blank value and exiting 0.
metric() {
local n
n=$(gh api -X GET "$1" -f q="$2" -q .total_count)
printf "%s: %s\n" "$3" "$n"
}
range="$since..$until"
echo "GitHub metrics for @$user ($since to $until)"
echo "-------------------------------------------------"
metric search/commits "author:$user author-date:$range" "Commits authored "
metric search/issues "type:pr author:$user created:$range" "PRs opened "
metric search/issues "type:pr author:$user merged:$range" "PRs merged "
# ponytail: GitHub search has no reviewed-at qualifier; this counts PRs *created* in range that user ever reviewed
metric search/issues "type:pr reviewed-by:$user created:$range" "PRs reviewed (created in range)"
metric search/issues "type:issue author:$user created:$range" "Issues opened "
metric search/issues "type:issue author:$user is:closed closed:$range" "Issues closed "
# ponytail: GNU date; inclusive day count. -u avoids DST off-by-one
days=$(( ( $(date -u -d "$until" +%s) - $(date -u -d "$since" +%s) ) / 86400 + 1 ))
printf "Days in range : %s\n" "$days"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment