Last active
          October 19, 2025 18:05 
        
      - 
      
- 
        Save joeskeen/e10345c6630e89a851ae43e880d99262 to your computer and use it in GitHub Desktop. 
    Check all Git repositories for uncommitted/unpushed changes
  
        
  
    
      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 | |
| ROOT_DIR="${1:-.}" | |
| ROOT_DIR="$(realpath "$ROOT_DIR")" | |
| echo "Scanning for Git repositories under: $ROOT_DIR" | |
| echo | |
| # Counters and path trackers | |
| total=0 | |
| clean=0 | |
| dirty=0 | |
| no_upstream=0 | |
| no_remote=0 | |
| repos_dirty=() | |
| repos_no_upstream=() | |
| repos_no_remote=() | |
| while read gitdir; do | |
| repo_dir="$(realpath "$(dirname "$gitdir")")" | |
| echo "π Checking: $repo_dir" | |
| cd "$repo_dir" || { echo " β Failed to enter $repo_dir"; echo; continue; } | |
| total=$((total + 1)) | |
| has_changes=false | |
| # Check for uncommitted changes | |
| if [[ -n $(git status --porcelain) ]]; then | |
| echo " π‘ Uncommitted changes" | |
| has_changes=true | |
| fi | |
| # Check for remote | |
| if [[ -z $(git remote) ]]; then | |
| echo " β οΈ No remote repository configured" | |
| no_remote=$((no_remote + 1)) | |
| repos_no_remote+=("$repo_dir") | |
| has_changes=true | |
| fi | |
| # Check for unpushed commits | |
| current_branch=$(git rev-parse --abbrev-ref HEAD) | |
| upstream=$(git rev-parse --abbrev-ref --symbolic-full-name "@{u}" 2>/dev/null) | |
| if [[ -n "$upstream" ]]; then | |
| ahead=$(git rev-list --count HEAD.."$upstream") | |
| if [[ "$ahead" -gt 0 ]]; then | |
| echo " π΄ $ahead unpushed commit(s)" | |
| has_changes=true | |
| fi | |
| else | |
| # Check if current commit is reachable from any branch with upstream | |
| current_commit=$(git rev-parse HEAD) | |
| merged_upstream=$(git for-each-ref --format='%(refname:short)' refs/heads | while read branch; do | |
| upstream_branch=$(git rev-parse --abbrev-ref --symbolic-full-name "$branch@{u}" 2>/dev/null) | |
| if [[ -n "$upstream_branch" ]]; then | |
| if git merge-base --is-ancestor "$current_commit" "$upstream_branch"; then | |
| echo "$branch" | |
| break | |
| fi | |
| fi | |
| done) | |
| if [[ -z "$merged_upstream" ]]; then | |
| echo " β οΈ No upstream set for current branch" | |
| no_upstream=$((no_upstream + 1)) | |
| repos_no_upstream+=("$repo_dir") | |
| has_changes=true | |
| fi | |
| fi | |
| if [[ "$has_changes" = false ]]; then | |
| echo " β All changes committed and pushed" | |
| clean=$((clean + 1)) | |
| else | |
| dirty=$((dirty + 1)) | |
| repos_dirty+=("$repo_dir") | |
| fi | |
| echo | |
| done < <(find "$ROOT_DIR" -type d -name ".git") | |
| # Summary | |
| echo "π Summary:" | |
| echo " Total repositories scanned: $total" | |
| echo " β Clean (committed & pushed): $clean" | |
| echo " π‘/π΄ Dirty (uncommitted or unpushed): $dirty" | |
| echo " β οΈ No upstream set: $no_upstream" | |
| echo " β οΈ No remote configured: $no_remote" | |
| echo | |
| # List paths | |
| if (( ${#repos_dirty[@]} > 0 )); then | |
| echo "π‘/π΄ Repositories with uncommitted or unpushed changes:" | |
| for path in "${repos_dirty[@]}"; do echo " - $path"; done | |
| echo | |
| fi | |
| if (( ${#repos_no_upstream[@]} > 0 )); then | |
| echo "β οΈ Repositories with no upstream set:" | |
| for path in "${repos_no_upstream[@]}"; do echo " - $path"; done | |
| echo | |
| fi | |
| if (( ${#repos_no_remote[@]} > 0 )); then | |
| echo "β οΈ Repositories with no remote configured:" | |
| for path in "${repos_no_remote[@]}"; do echo " - $path"; done | |
| echo | |
| fi | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment