Skip to content

Instantly share code, notes, and snippets.

@spyesx
Last active February 24, 2025 13:29
Show Gist options
  • Save spyesx/a068c93e0dabe731d293c3e7af31e62d to your computer and use it in GitHub Desktop.
Save spyesx/a068c93e0dabe731d293c3e7af31e62d to your computer and use it in GitHub Desktop.
Standalone Bash script to recursively scan directories and identify Git repositories with unpushed changes or local modifications.

Standalone Bash script to recursively scan directories and identify Git repositories with unpushed changes or local modifications.

Features

  • Recursive Search: Traverses directories from a configurable root path to find .git directories.
  • Checks for Unpushed Commits: Detects repositories where the local branch is ahead of the origin remote.
  • Detects Local Changes: Reports repositories with uncommitted modifications (staged, unstaged, untracked).
  • Standalone: No external dependencies beyond bash and git.
  • Configurable Search Path: Easily modify the DEFAULT_GIT_CLONE_PATH variable within the script to change the root directory for searching.
  • Summary Report: Provides a summary at the end indicating the total repositories scanned and those with pending changes.
  • Exit Code: Returns a non-zero exit code if any repositories with pending changes are found, useful for scripting and automation.

Configuration

Modify the DEFAULT_GIT_CLONE_PATH="$HOME/git" variable at the top of the script to change the default directory to search for Git repositories.

Usage

  1. Save the script to a file (e.g., git-report.sh).
  2. Make it executable: chmod +x git-report.sh.
  3. Run it: ./git-report.sh.
#!/usr/bin/env bash
# Standalone Git Repository Report Script
# Configuration (can be modified here directly)
DEFAULT_GIT_CLONE_PATH="$HOME/repositories" # Default path to search for git repos
# --- Helper Functions (Standalone implementations) ---
# For standalone, let's use simple echo with prefixes instead of log_warning/log_info
log_warning() {
echo "[WARNING] $@"
}
log_info() {
echo "[INFO] $@"
}
# --- Script Functions (Modified for standalone) ---
fn_git_report__look_for_git_repo_recursively() {
# inspired by https://stackoverflow.com/a/23344072
local folder
local git_repositories
folder="$1"
# Find command that handle spaces in paths
git_repositories=$(find "$folder" -type d \( -exec test -e {}/.ignore \; -prune \) -o \( -exec test -d {}/.git \; -prune -print0 \) | tr '\0' '\n')
# Filter out empty lines from find output (if any)
git_repositories=$(echo "$git_repositories" | grep -v '^$')
echo "$git_repositories"
}
fn_git_report__process_repo() {
local repo_path
local status
repo_path="$1"
if ! cd "$repo_path"; then
log_warning "Could not change directory to: $repo_path. Skipping."
return 1 # Indicate failure to cd, but continue script
fi
status="$(git status -s)"
if [ -n "$status" ]; then
log_warning "Git repo with pending changes: $repo_path"
echo ""
echo " Path: $repo_path"
remote_url=$(git remote get-url origin 2>/dev/null) # Capture remote URL or handle error if no remote
if [ -n "$remote_url" ]; then
echo " Remote: $remote_url"
else
echo " Remote: No remote configured (origin)"
fi
branch_name=$(git branch --show-current 2>/dev/null) # Capture branch name or handle error if detached HEAD
if [ -n "$branch_name" ]; then
echo " Branch: $branch_name"
else
echo " Branch: Detached HEAD"
fi
while IFS= read -r line; do
printf " %s\n" "$line"
done <<<"$status"
echo ""
else
log_info "Git repo clean: $repo_path"
fi
return 0 # Indicate successful processing of repo
}
fn_git_report() {
local git_default_clone_path
git_default_clone_path="${DEFAULT_GIT_CLONE_PATH}" # Use the configured default
if [ ! -d "$git_default_clone_path" ]; then
log_warning "Default git clone path '$git_default_clone_path' does not exist or is not a directory."
return 1 # Indicate failure, path is invalid
fi
local repos_found
repos_found=0
local repos_with_pending_changes=0
while IFS= read -r repo; do
repos_found=$((repos_found + 1))
if fn_git_report__process_repo "$repo"; then
: # Processed successfully, no need to increment pending changes count here as it's already logged in process_repo
: # if process_repo returns 0, it's considered processed. if it returned 1, it failed to cd and was skipped.
status_check=$(git status -s 2>/dev/null) # Re-check status within process_repo context, capture status again
if [ -n "$status_check" ]; then
repos_with_pending_changes=$((repos_with_pending_changes + 1))
fi
else
log_warning "Failed to process repo at '$repo'. Skipping."
fi
done <<<"$(fn_git_report__look_for_git_repo_recursively "$git_default_clone_path")"
echo ""
echo "--- Summary ---"
echo "Total git repositories scanned: $repos_found"
echo "Repositories with pending changes: $repos_with_pending_changes"
if [ "$repos_with_pending_changes" -gt 0 ]; then
return 1 # Indicate that there were repos with pending changes (non-zero exit code)
else
return 0 # Indicate success, no repos with pending changes (zero exit code)
fi
}
# --- Main execution ---
fn_git_report
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment