Skip to content

Instantly share code, notes, and snippets.

@joeskeen
Last active October 19, 2025 18:05
Show Gist options
  • Save joeskeen/e10345c6630e89a851ae43e880d99262 to your computer and use it in GitHub Desktop.
Save joeskeen/e10345c6630e89a851ae43e880d99262 to your computer and use it in GitHub Desktop.
Check all Git repositories for uncommitted/unpushed changes
#!/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