Last active
January 16, 2026 23:03
-
-
Save nelsoneldoro/9f6f54cce2631e6990b6ad8a7915a354 to your computer and use it in GitHub Desktop.
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 | |
| set -e | |
| # Script to list stale branches (branches that haven't been updated in a specified time period) | |
| # Default: 2 months (60 days) | |
| help() { | |
| echo "List stale branches that haven't been updated in a specified time period." | |
| echo "" | |
| echo "Usage: stale-branches [OPTIONS]" | |
| echo "" | |
| echo "Options:" | |
| echo " -d, --days DAYS Number of days to consider a branch stale (default: 60)" | |
| echo " -l, --local Only show local branches" | |
| echo " -r, --remote Only show remote branches (default)" | |
| echo " -a, --all Show both local and remote branches" | |
| echo " -m, --merged Only show branches that have been merged" | |
| echo " -n, --no-merged Only show branches that have not been merged" | |
| echo " -c, --csv Output in CSV format" | |
| echo " -o, --output FILE Output CSV to file (requires --csv)" | |
| echo " -h, --help Show this help message" | |
| echo "" | |
| echo "Examples:" | |
| echo " stale-branches # List remote branches stale for 60+ days" | |
| echo " stale-branches -d 30 # List branches stale for 30+ days" | |
| echo " stale-branches -a -d 90 # List all branches stale for 90+ days" | |
| echo " stale-branches -l -m # List merged local branches stale for 60+ days" | |
| echo " stale-branches --csv # Output as CSV to stdout" | |
| echo " stale-branches --csv -o output.csv # Output as CSV to file" | |
| echo "" | |
| echo "Note: When checking remote branches, consider running 'git fetch --prune' first to ensure" | |
| echo " you have the latest remote branch information." | |
| exit 0 | |
| } | |
| # Default values | |
| DAYS=60 | |
| BRANCH_TYPE="remote" | |
| SHOW_MERGED="" | |
| CUTOFF_DATE="" | |
| CSV_OUTPUT=false | |
| OUTPUT_FILE="" | |
| # Parse arguments | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| -d|--days) | |
| DAYS="$2" | |
| shift 2 | |
| ;; | |
| -l|--local) | |
| BRANCH_TYPE="local" | |
| shift | |
| ;; | |
| -r|--remote) | |
| BRANCH_TYPE="remote" | |
| shift | |
| ;; | |
| -a|--all) | |
| BRANCH_TYPE="all" | |
| shift | |
| ;; | |
| -m|--merged) | |
| SHOW_MERGED="--merged" | |
| shift | |
| ;; | |
| -n|--no-merged) | |
| SHOW_MERGED="--no-merged" | |
| shift | |
| ;; | |
| -c|--csv) | |
| CSV_OUTPUT=true | |
| shift | |
| ;; | |
| -o|--output) | |
| OUTPUT_FILE="$2" | |
| CSV_OUTPUT=true | |
| shift 2 | |
| ;; | |
| -h|--help) | |
| help | |
| ;; | |
| *) | |
| echo "Unknown option: $1" | |
| help | |
| ;; | |
| esac | |
| done | |
| # Validate CSV output file option | |
| if [ -n "$OUTPUT_FILE" ] && [ "$CSV_OUTPUT" = false ]; then | |
| echo "Error: --output requires --csv option" | |
| exit 1 | |
| fi | |
| # Calculate cutoff date | |
| if [[ "$OSTYPE" == "darwin"* ]]; then | |
| # macOS | |
| CUTOFF_DATE=$(date -v-${DAYS}d +%s 2>/dev/null || date -j -v-${DAYS}d +%s) | |
| else | |
| # Linux | |
| CUTOFF_DATE=$(date -d "${DAYS} days ago" +%s) | |
| fi | |
| if [ -z "$CUTOFF_DATE" ]; then | |
| echo "Error: Unable to calculate cutoff date. Please ensure 'date' command is available." | |
| exit 1 | |
| fi | |
| # Function to check if a branch is stale | |
| is_stale() { | |
| local branch=$1 | |
| local last_commit_date=$(git log -1 --format=%ct "$branch" 2>/dev/null) | |
| if [ -z "$last_commit_date" ]; then | |
| return 1 | |
| fi | |
| if [ "$last_commit_date" -lt "$CUTOFF_DATE" ]; then | |
| return 0 | |
| else | |
| return 1 | |
| fi | |
| } | |
| # Function to escape CSV fields | |
| escape_csv() { | |
| local field="$1" | |
| # If field contains comma, quote, or newline, wrap in quotes and escape quotes | |
| if [[ "$field" =~ [,\"$'\n'] ]]; then | |
| field="${field//\"/\"\"}" | |
| field="\"$field\"" | |
| fi | |
| echo "$field" | |
| } | |
| # Function to get branch info as array | |
| get_branch_info() { | |
| local branch=$1 | |
| git log --no-merges -n 1 --format="%ci|%cr|%an|%ae|%s" "$branch" 2>/dev/null | |
| } | |
| # Function to format and display branch info | |
| display_branch() { | |
| local branch=$1 | |
| local info=$(get_branch_info "$branch") | |
| if [ -n "$info" ]; then | |
| IFS='|' read -r date relative author email subject <<< "$info" | |
| if [ -n "$date" ]; then | |
| if [ "$CSV_OUTPUT" = true ]; then | |
| echo "$(escape_csv "$date"),$(escape_csv "$relative"),$(escape_csv "$author"),$(escape_csv "$email"),$(escape_csv "$subject"),$(escape_csv "$branch")" | |
| else | |
| printf "%-25s %-15s %-30s %s\n" "$date" "$relative" "$author" "$branch" | |
| fi | |
| fi | |
| fi | |
| } | |
| # Main execution | |
| STALE_COUNT=0 | |
| STALE_BRANCHES=() | |
| # Collect stale branches | |
| if [ "$BRANCH_TYPE" = "local" ] || [ "$BRANCH_TYPE" = "all" ]; then | |
| for branch in $(git branch $SHOW_MERGED 2>/dev/null | sed 's/^[ *]*//' | grep -v HEAD); do | |
| if is_stale "$branch"; then | |
| STALE_BRANCHES+=("$branch") | |
| ((STALE_COUNT++)) | |
| fi | |
| done | |
| fi | |
| if [ "$BRANCH_TYPE" = "remote" ] || [ "$BRANCH_TYPE" = "all" ]; then | |
| for branch in $(git branch -r $SHOW_MERGED 2>/dev/null | sed 's/^[ *]*//' | grep -v HEAD); do | |
| if is_stale "$branch"; then | |
| STALE_BRANCHES+=("$branch") | |
| ((STALE_COUNT++)) | |
| fi | |
| done | |
| fi | |
| # Output results | |
| if [ "$CSV_OUTPUT" = true ]; then | |
| # CSV output | |
| { | |
| # CSV header | |
| echo "Last Commit Date,Relative Time,Author,Email,Subject,Branch" | |
| # CSV rows | |
| for branch in "${STALE_BRANCHES[@]}"; do | |
| display_branch "$branch" | |
| done | |
| } > "${OUTPUT_FILE:-/dev/stdout}" | |
| if [ -n "$OUTPUT_FILE" ]; then | |
| echo "CSV output written to: $OUTPUT_FILE" >&2 | |
| echo "Total stale branches: $STALE_COUNT" >&2 | |
| fi | |
| else | |
| # Table output | |
| echo "Stale branches (not updated in ${DAYS} days or more)" | |
| echo "==================================================" | |
| echo "" | |
| printf "%-25s %-15s %-30s %s\n" "Last Commit Date" "Relative" "Author" "Branch" | |
| echo "--------------------------------------------------------------------------------" | |
| for branch in "${STALE_BRANCHES[@]}"; do | |
| display_branch "$branch" | |
| done | |
| echo "" | |
| if [ $STALE_COUNT -eq 0 ]; then | |
| echo "No stale branches found." | |
| else | |
| echo "Total stale branches: $STALE_COUNT" | |
| fi | |
| fi |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Consider running
git fetch --pruneto report branches that actually exist on the remote