Git Line Counter Script

The 'count-lines.sh' script is a powerful Bash tool designed to analyze Git repositories and calculate the total number of files changed, lines inserted, and lines deleted by a specified author over a given time period. By traversing a folder containing one or multiple Git repositories, it aggregates commit statistics using `git log --shortstat`, supporting both single repositories and nested directory structures. With robust error handling, cross-platform date calculations, and the ability to filter by author name or email (including multiple emails via regex), it provides detailed per-repository and summary reports. Ideal for developers and teams, this script helps track individual contributions across projects efficiently.

Script

count-lines.sh
#!/bin/bash

# Script created by Kirill Zubovsky (Grok, really) to count lines of code in a given folder with a git repository
# It counts lines of code inserted and deleted by a given author in a given time period

# Configuration
FOLDER_PATH="$1"          # Path to folder containing repositories or a single repository
DAYS="$2"                 # Number of days to look back
AUTHOR="$3"               # Git author name or email (regex for multiple emails)

# Check if arguments are provided
if [ -z "$FOLDER_PATH" ] || [ -z "$DAYS" ] || [ -z "$AUTHOR" ]; then
  echo "Usage: $0 <folder_or_repo_path> <days> <author_name_or_email>"
  echo "Example: $0 ~/Dev 90 'your_github_email@email.com\|your_other_github_email@email.com'"
  exit 1
fi

# Resolve FOLDER_PATH to absolute path
FOLDER_PATH=$(realpath "$FOLDER_PATH" 2>/dev/null || echo "$FOLDER_PATH")

# Calculate explicit date for --since
SINCE_DATE=$(date -d "-$DAYS days" +%Y-%m-%d 2>/dev/null || date -v -${DAYS}d +%Y-%m-%d)

# Initialize counters
total_files=0
total_inserted=0
total_deleted=0

# Function to process a single repository
process_repo() {
  local repo="$1"
  echo "Processing repository: $repo"
  if ! cd "$repo" 2>/dev/null; then
    echo "  Error: Cannot access repository directory"
    return
  fi
  # Check if it's a valid Git repository
  if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
    echo "  Error: Not a valid Git repository"
    cd - >/dev/null
    return
  fi
  # Fetch latest commits
  echo "  Fetching latest commits..."
  git fetch origin >/dev/null 2>&1 || echo "  Warning: Failed to fetch updates"
  # List author emails for debugging
  echo "  Authors in this repository:"
  git log --pretty="%an <%ae>" | sort | uniq
  # Run git log command across all branches
  echo "  Running git log for author '$AUTHOR' since $SINCE_DATE..."
  stats=$(git log --shortstat --all --author="$AUTHOR" --since="$SINCE_DATE" \
    | grep -E "file[s]* changed" \
    | awk '{files+=$1; inserted+=$4; deleted+=$6} END {print files, inserted, deleted}')
  
  # Extract stats
  local files=0 inserted=0 deleted=0
  if [ -n "$stats" ]; then
    read files inserted deleted <<< "$stats"
    files=${files:-0}
    inserted=${inserted:-0}
    deleted=${deleted:-0}
    echo "  Files changed: $files, Lines inserted: $inserted, Lines deleted: $deleted"
  else
    echo "  No changes found for '$AUTHOR' since $SINCE_DATE."
  fi
  # Update global totals
  total_files=$((total_files + files))
  total_inserted=$((total_inserted + inserted))
  total_deleted=$((total_deleted + deleted))
  echo "  Current totals: files=$total_files, inserted=$total_inserted, deleted=$total_deleted"
  cd - >/dev/null
}

# Check if the path is a single Git repository
if [ -d "$FOLDER_PATH/.git" ] && git -C "$FOLDER_PATH" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  process_repo "$FOLDER_PATH"
else
  # Collect Git repositories into an array without mapfile
  repos=()
  while IFS= read -r repo; do
    repos+=("$repo")
  done < <(find "$FOLDER_PATH" -type d -name ".git" -exec dirname {} \;)
  if [ ${#repos[@]} -eq 0 ]; then
    echo "No Git repositories found in $FOLDER_PATH"
  else
    echo "Found ${#repos[@]} Git repositories:"
    for repo in "${repos[@]}"; do
      echo "  $repo"
      process_repo "$repo"
    done
  fi
fi

# Print summary
echo -e "\nSummary for $FOLDER_PATH:"
echo "Total files changed: $total_files"
echo "Total lines inserted: $total_inserted"
echo "Total lines deleted: $total_deleted"

Usage Example:

./count-lines.sh ~/Dev 90 'your_github_email@email.com\|your_other_github_email@email.com'

This will count lines changed by either email address in any git repositories found in the ~/Dev directory over the last 90 days.