Created
March 6, 2026 22:21
-
-
Save balanza/135a5a66048ce755cfd26e74b0823347 to your computer and use it in GitHub Desktop.
Calculate the list of contributor that will lose their contribution after files are deleted
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
| #!/usr/bin/env bash | |
| # | |
| # lost-contributors.sh | |
| # | |
| # 1. Lists files deleted in the current branch compared to a target branch | |
| # 2. Finds contributors who ever participated in those deleted files (full history) | |
| # but have no lines remaining in any surviving file | |
| # | |
| # Usage: | |
| # ./lost-contributors.sh [target_branch] | |
| # | |
| # Defaults: | |
| # target_branch = main | |
| # | |
| # Example: | |
| # git checkout feature/remove-legacy-code | |
| # ./lost-contributors.sh main | |
| set -euo pipefail | |
| TARGET_BRANCH="${1:-main}" | |
| CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD") | |
| echo "==============================================" | |
| echo " Lost Contributors Detector" | |
| echo "==============================================" | |
| echo "" | |
| echo " Target branch : $TARGET_BRANCH" | |
| echo " Current branch : $CURRENT_BRANCH" | |
| echo "" | |
| # ── Step 1: Find files deleted in the current branch vs target ── | |
| echo "[1/4] Finding deleted files..." | |
| echo "" | |
| deleted_files_list=$(mktemp) | |
| git diff --name-only --diff-filter=D "$TARGET_BRANCH"..."$CURRENT_BRANCH" > "$deleted_files_list" | |
| deleted_count=$(wc -l < "$deleted_files_list" | tr -d ' ') | |
| if [ "$deleted_count" -eq 0 ]; then | |
| echo " ✅ No files were deleted. Nothing to check." | |
| rm -f "$deleted_files_list" | |
| exit 0 | |
| fi | |
| echo " $deleted_count file(s) deleted:" | |
| while IFS= read -r f; do | |
| echo " - $f" | |
| done < "$deleted_files_list" | |
| echo "" | |
| # ── Step 2: Get ALL contributors who ever touched the deleted files (full history) ── | |
| echo "[2/4] Collecting all historical contributors of deleted files..." | |
| echo "" | |
| deleted_contributors_file=$(mktemp) | |
| while IFS= read -r file; do | |
| git log --all --follow --format='<%ae>' -- "$file" 2>/dev/null | |
| done < "$deleted_files_list" | sort -u > "$deleted_contributors_file" | |
| deleted_contrib_count=$(wc -l < "$deleted_contributors_file" | tr -d ' ') | |
| echo " Found $deleted_contrib_count unique contributor(s) who ever touched deleted files." | |
| echo "" | |
| if [ "$deleted_contrib_count" -eq 0 ]; then | |
| echo " ✅ No contributors found in deleted files." | |
| rm -f "$deleted_files_list" "$deleted_contributors_file" | |
| exit 0 | |
| fi | |
| # ── Step 3: For each contributor in deleted files, check if they have ── | |
| # ── lines in any surviving file on the current branch ── | |
| echo "[3/4] Checking if those contributors have lines in surviving files..." | |
| echo " (this may take a while on large repos)" | |
| echo "" | |
| # Get the full list of files on the current branch (the surviving files) | |
| surviving_files_list=$(mktemp) | |
| git ls-tree -r --name-only "$CURRENT_BRANCH" > "$surviving_files_list" | |
| # Blame all surviving files once and collect all contributor emails | |
| surviving_contributors_file=$(mktemp) | |
| while IFS= read -r file; do | |
| git blame --porcelain "$CURRENT_BRANCH" -- "$file" 2>/dev/null \ | |
| | grep "^author-mail " \ | |
| | sed 's/^author-mail //' | |
| done < "$surviving_files_list" | sort -u > "$surviving_contributors_file" | |
| surviving_contrib_count=$(wc -l < "$surviving_contributors_file" | tr -d ' ') | |
| echo " Found $surviving_contrib_count unique contributor(s) in surviving files." | |
| echo "" | |
| # ── Step 4: Find contributors only in deleted files (not in surviving) ── | |
| echo "[4/4] Comparing..." | |
| echo "" | |
| lost_file=$(mktemp) | |
| comm -23 "$deleted_contributors_file" "$surviving_contributors_file" > "$lost_file" | |
| lost_count=$(wc -l < "$lost_file" | tr -d ' ') | |
| echo "==============================================" | |
| echo " Results" | |
| echo "==============================================" | |
| echo "" | |
| if [ "$lost_count" -eq 0 ]; then | |
| echo " ✅ No contributors will be fully removed." | |
| echo " All contributors in deleted files also have lines elsewhere." | |
| else | |
| echo "## Lost contributors" | |
| echo "" | |
| echo "$lost_count contributor(s) participated in deleted files and have no lines remaining in the surviving codebase:" | |
| echo "" | |
| # Collect sorted usernames into an array | |
| usernames=() | |
| while IFS= read -r email; do | |
| clean_email=$(echo "$email" | tr -d '<>') | |
| if echo "$clean_email" | grep -q '@users\.noreply\.github\.com$'; then | |
| gh_user=$(echo "$clean_email" | sed 's/@users\.noreply\.github\.com$//' | sed 's/^[0-9]*+//') | |
| else | |
| gh_user=$(echo "$clean_email" | cut -d'@' -f1) | |
| fi | |
| usernames+=("@${gh_user}") | |
| done < "$lost_file" | |
| # Sort alphabetically (case-insensitive) | |
| IFS=$'\n' sorted=($(printf '%s\n' "${usernames[@]}" | sort -f)); unset IFS | |
| # Output as a 4-column markdown table (no header) | |
| echo "| | | | |" | |
| echo "|---|---|---|---|" | |
| col=0 | |
| row="" | |
| for user in "${sorted[@]}"; do | |
| row="${row}| ${user} " | |
| col=$((col + 1)) | |
| if [ "$col" -eq 4 ]; then | |
| echo "${row}|" | |
| row="" | |
| col=0 | |
| fi | |
| done | |
| # Handle remaining users in the last incomplete row | |
| if [ "$col" -gt 0 ]; then | |
| while [ "$col" -lt 4 ]; do | |
| row="${row}| " | |
| col=$((col + 1)) | |
| done | |
| echo "${row}|" | |
| fi | |
| fi | |
| echo "" | |
| # Cleanup | |
| rm -f "$deleted_files_list" "$deleted_contributors_file" \ | |
| "$surviving_files_list" "$surviving_contributors_file" "$lost_file" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment