Created
January 7, 2026 01:08
-
-
Save edmondop/c0a65f786cdec42d8021e6a435ca128f to your computer and use it in GitHub Desktop.
Github contribution stats
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # GitHub Contribution Stats Fetcher (SSO-Compatible) | |
| # Usage: ./gh-contrib-stats.sh <username> <year> <org> | |
| # Check for required arguments | |
| if [ $# -lt 3 ]; then | |
| echo "Usage: $0 <username> <year> <org>" | |
| echo "" | |
| echo "Example: $0 octocat 2025 github" | |
| echo "" | |
| echo "Arguments:" | |
| echo " username - GitHub username" | |
| echo " year - Year to query (e.g., 2025)" | |
| echo " org - GitHub organization name" | |
| exit 1 | |
| fi | |
| USERNAME="$1" | |
| YEAR="$2" | |
| ORG="$3" | |
| echo "π GitHub Stats for @${USERNAME} in ${YEAR} (org: ${ORG})" | |
| echo "" | |
| # PRs created | |
| PR_COUNT=$(gh api -X GET "/search/issues" \ | |
| -f q="author:${USERNAME} type:pr org:${ORG} created:${YEAR}-01-01..${YEAR}-12-31" \ | |
| --jq '.total_count') | |
| # First and last PR | |
| PR_FIRST=$(gh api -X GET "/search/issues" \ | |
| -f q="author:${USERNAME} type:pr org:${ORG} created:${YEAR}-01-01..${YEAR}-12-31" \ | |
| -f sort="created" -f order="asc" -f per_page="1" \ | |
| --jq '.items[0].created_at // empty' | cut -d'T' -f1) | |
| PR_LAST=$(gh api -X GET "/search/issues" \ | |
| -f q="author:${USERNAME} type:pr org:${ORG} created:${YEAR}-01-01..${YEAR}-12-31" \ | |
| -f sort="created" -f order="desc" -f per_page="1" \ | |
| --jq '.items[0].created_at // empty' | cut -d'T' -f1) | |
| # PRs reviewed | |
| REVIEW_COUNT=$(gh api -X GET "/search/issues" \ | |
| -f q="reviewed-by:${USERNAME} type:pr org:${ORG} created:${YEAR}-01-01..${YEAR}-12-31" \ | |
| --jq '.total_count') | |
| REVIEW_FIRST=$(gh api -X GET "/search/issues" \ | |
| -f q="reviewed-by:${USERNAME} type:pr org:${ORG} created:${YEAR}-01-01..${YEAR}-12-31" \ | |
| -f sort="created" -f order="asc" -f per_page="1" \ | |
| --jq '.items[0].created_at // empty' | cut -d'T' -f1) | |
| REVIEW_LAST=$(gh api -X GET "/search/issues" \ | |
| -f q="reviewed-by:${USERNAME} type:pr org:${ORG} created:${YEAR}-01-01..${YEAR}-12-31" \ | |
| -f sort="created" -f order="desc" -f per_page="1" \ | |
| --jq '.items[0].created_at // empty' | cut -d'T' -f1) | |
| # Issues created | |
| ISSUE_COUNT=$(gh api -X GET "/search/issues" \ | |
| -f q="author:${USERNAME} type:issue org:${ORG} created:${YEAR}-01-01..${YEAR}-12-31" \ | |
| --jq '.total_count') | |
| ISSUE_FIRST=$(gh api -X GET "/search/issues" \ | |
| -f q="author:${USERNAME} type:issue org:${ORG} created:${YEAR}-01-01..${YEAR}-12-31" \ | |
| -f sort="created" -f order="asc" -f per_page="1" \ | |
| --jq '.items[0].created_at // empty' | cut -d'T' -f1) | |
| ISSUE_LAST=$(gh api -X GET "/search/issues" \ | |
| -f q="author:${USERNAME} type:issue org:${ORG} created:${YEAR}-01-01..${YEAR}-12-31" \ | |
| -f sort="created" -f order="desc" -f per_page="1" \ | |
| --jq '.items[0].created_at // empty' | cut -d'T' -f1) | |
| # Commits | |
| COMMIT_COUNT=$(gh api -X GET "/search/commits" \ | |
| -H "Accept: application/vnd.github.cloak-preview" \ | |
| -f q="author:${USERNAME} org:${ORG} committer-date:${YEAR}-01-01..${YEAR}-12-31" \ | |
| --jq '.total_count') | |
| COMMIT_FIRST=$(gh api -X GET "/search/commits" \ | |
| -H "Accept: application/vnd.github.cloak-preview" \ | |
| -f q="author:${USERNAME} org:${ORG} committer-date:${YEAR}-01-01..${YEAR}-12-31" \ | |
| -f sort="committer-date" -f order="asc" -f per_page="1" \ | |
| --jq '.items[0].commit.committer.date // empty' | cut -d'T' -f1) | |
| COMMIT_LAST=$(gh api -X GET "/search/commits" \ | |
| -H "Accept: application/vnd.github.cloak-preview" \ | |
| -f q="author:${USERNAME} org:${ORG} committer-date:${YEAR}-01-01..${YEAR}-12-31" \ | |
| -f sort="committer-date" -f order="desc" -f per_page="1" \ | |
| --jq '.items[0].commit.committer.date // empty' | cut -d'T' -f1) | |
| # Calculate total | |
| TOTAL=$((COMMIT_COUNT + PR_COUNT + REVIEW_COUNT + ISSUE_COUNT)) | |
| # Find overall first and last activity dates | |
| ALL_DATES=("$COMMIT_FIRST" "$PR_FIRST" "$REVIEW_FIRST" "$ISSUE_FIRST") | |
| ALL_DATES_LAST=("$COMMIT_LAST" "$PR_LAST" "$REVIEW_LAST" "$ISSUE_LAST") | |
| FIRST_ACTIVITY=$(printf '%s\n' "${ALL_DATES[@]}" | grep -v '^$' | sort | head -1) | |
| LAST_ACTIVITY=$(printf '%s\n' "${ALL_DATES_LAST[@]}" | grep -v '^$' | sort -r | head -1) | |
| # Display results | |
| echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| echo " CONTRIBUTION SUMMARY" | |
| echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| echo "" | |
| printf " %-20s %8s %14s %14s\n" "TYPE" "COUNT" "FIRST" "LAST" | |
| echo " βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| printf " π» %-17s %8d %14s %14s\n" "Commits" "$COMMIT_COUNT" "${COMMIT_FIRST:--}" "${COMMIT_LAST:--}" | |
| printf " π %-17s %8d %14s %14s\n" "Pull Requests" "$PR_COUNT" "${PR_FIRST:--}" "${PR_LAST:--}" | |
| printf " π %-17s %8d %14s %14s\n" "Reviews" "$REVIEW_COUNT" "${REVIEW_FIRST:--}" "${REVIEW_LAST:--}" | |
| printf " π %-17s %8d %14s %14s\n" "Issues" "$ISSUE_COUNT" "${ISSUE_FIRST:--}" "${ISSUE_LAST:--}" | |
| echo " βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| printf " π %-17s %8d %14s %14s\n" "TOTAL" "$TOTAL" "${FIRST_ACTIVITY:--}" "${LAST_ACTIVITY:--}" | |
| echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" | |
| # Calculate percentages if total > 0 | |
| if [ "$TOTAL" -gt 0 ]; then | |
| echo "" | |
| echo " BREAKDOWN:" | |
| echo " βββββββββββββββββββββββββββββββββββββββββ" | |
| printf " π» Commits: %3d%% " "$((COMMIT_COUNT * 100 / TOTAL))" | |
| printf "%-30s\n" "$(printf 'β%.0s' $(seq 1 $((COMMIT_COUNT * 30 / TOTAL))))" | |
| printf " π PRs: %3d%% " "$((PR_COUNT * 100 / TOTAL))" | |
| printf "%-30s\n" "$(printf 'β%.0s' $(seq 1 $((PR_COUNT * 30 / TOTAL))))" | |
| printf " π Reviews: %3d%% " "$((REVIEW_COUNT * 100 / TOTAL))" | |
| printf "%-30s\n" "$(printf 'β%.0s' $(seq 1 $((REVIEW_COUNT * 30 / TOTAL))))" | |
| printf " π Issues: %3d%% " "$((ISSUE_COUNT * 100 / TOTAL))" | |
| printf "%-30s\n" "$(printf 'β%.0s' $(seq 1 $((ISSUE_COUNT * 30 / TOTAL))))" | |
| echo "" | |
| fi | |
| # Calculate active days | |
| if [ -n "$FIRST_ACTIVITY" ] && [ -n "$LAST_ACTIVITY" ]; then | |
| if [[ "$OSTYPE" == "darwin"* ]]; then | |
| FIRST_SEC=$(date -j -f "%Y-%m-%d" "$FIRST_ACTIVITY" "+%s" 2>/dev/null) | |
| LAST_SEC=$(date -j -f "%Y-%m-%d" "$LAST_ACTIVITY" "+%s" 2>/dev/null) | |
| else | |
| FIRST_SEC=$(date -d "$FIRST_ACTIVITY" "+%s" 2>/dev/null) | |
| LAST_SEC=$(date -d "$LAST_ACTIVITY" "+%s" 2>/dev/null) | |
| fi | |
| if [ -n "$FIRST_SEC" ] && [ -n "$LAST_SEC" ]; then | |
| DAYS_SPAN=$(( (LAST_SEC - FIRST_SEC) / 86400 + 1 )) | |
| AVG_PER_DAY=$(echo "scale=2; $TOTAL / $DAYS_SPAN" | bc) | |
| echo " π Active span: ${DAYS_SPAN} days (${FIRST_ACTIVITY} β ${LAST_ACTIVITY})" | |
| echo " β‘ Average: ${AVG_PER_DAY} contributions/day" | |
| echo "" | |
| fi | |
| fi | |
| echo "βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment