Skip to content

Instantly share code, notes, and snippets.

@fegue
Last active December 10, 2024 10:14
Show Gist options
  • Save fegue/729c4d9dc5880f841daac160a6e20649 to your computer and use it in GitHub Desktop.
Save fegue/729c4d9dc5880f841daac160a6e20649 to your computer and use it in GitHub Desktop.
Check git status of all branches and optinally recursively all repositories in a directory
#!/usr/bin/env bash
# Git Repository Status Checker
# ===========================
#
# This script checks the status of one or multiple git repositories and provides
# detailed information about:
# - Uncommitted changes
# - Stashed changes
# - Branch status (ahead/behind remote)
# - Remote branch existence
# - Working directory status
#
# Usage:
# ./git_repo_status.sh [-r] /path/to/directory
#
# Options:
# -r Recursively check all git repositories in the directory
#
# Example:
# Single repo: ./git_repo_status.sh ~/projects/myrepo
# Multiple repos: ./git_repo_status.sh -r ~/projects
#
# Note: The script requires git to be installed and available in PATH.
#
# Created with the assistance of GitHub Copilot (Claude 3.5. Sonnet)
# Last updated: 2024-12-10
# License: MIT
#
# ===========================
set -e
# Define color codes
REPO_COLOR='\033[1;35m' # Bold Magenta for repos
BRANCH_COLOR='\033[0;36m' # Cyan for branches
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
# Store the original working directory
ORIGINAL_PWD=$(pwd)
# Function to find git repositories (modified to use absolute paths)
find_git_repos() {
local search_dir="$(cd "$1" && pwd)"
find "$search_dir" -type d -name ".git" -exec dirname {} \;
}
# Function to check a single repository
check_repository() {
local repo_dir="$(cd "$1" && pwd)"
echo -e "${REPO_COLOR}============================================="
echo -e "Repository: $repo_dir"
echo -e "==============================================${NC}"
cd "$repo_dir"
git fetch --all --prune
CURRENT_BRANCH=$(git branch --show-current || echo "main")
for branch in $(git branch --format='%(refname:short)'); do
git checkout -q "$branch"
echo -e "${BRANCH_COLOR}---------------------------------------"
echo -e "Branch: $branch"
echo -e "---------------------------------------${NC}"
# Show working directory status (short + branch info)
git status --short --branch
# Check for uncommitted changes
if [ -n "$(git status --porcelain)" ]; then
echo -e "${RED}There are uncommitted changes.${NC}"
else
echo -e "${GREEN}No uncommitted changes.${NC}"
fi
# Check for stashed changes
if [ -n "$(git stash list)" ]; then
echo -e "${YELLOW}There are stashed changes:${NC}"
git stash list
else
echo -e "${GREEN}No stashed changes.${NC}"
fi
# Determine the upstream branch (if any)
UPSTREAM=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || true)
# If there's no upstream branch, skip the remote comparison
if [ -z "$UPSTREAM" ]; then
echo "No upstream branch set for $branch. Skipping remote checks."
echo
continue
fi
# Check if the upstream branch actually exists on the remote
REMOTE_NAME="${UPSTREAM%%/*}"
REMOTE_BRANCH="${UPSTREAM#*/}"
# If listing the remote branch fails, it means the remote branch may no longer exist
if ! git ls-remote --exit-code "$REMOTE_NAME" "refs/heads/$REMOTE_BRANCH" >/dev/null 2>&1; then
echo "Upstream branch $UPSTREAM no longer exists on the remote. Ignoring this branch."
echo
continue
fi
# Compare local and remote branches
LOCAL=$(git rev-parse @)
REMOTE=$(git rev-parse @{u})
BASE=$(git merge-base @ @{u})
if [ "$LOCAL" = "$REMOTE" ]; then
echo -e "${GREEN}No unpushed or unpulled commits for $branch.${NC}"
elif [ "$LOCAL" = "$BASE" ]; then
echo -e "${RED}Branch $branch is behind its remote. Commits need to be pulled.${NC}"
elif [ "$REMOTE" = "$BASE" ]; then
echo -e "${YELLOW}Branch $branch is ahead of its remote. Commits need to be pushed.${NC}"
else
echo -e "${RED}Branch $branch has diverged from its remote.${NC}"
fi
echo
done
git checkout -q "$CURRENT_BRANCH"
# Return to original directory after checking repository
cd "$ORIGINAL_PWD"
}
# Parse command line arguments
RECURSIVE=false
while getopts "r" opt; do
case $opt in
r) RECURSIVE=true ;;
*) echo "Usage: $0 [-r] /path/to/directory" >&2
exit 1 ;;
esac
done
shift $((OPTIND-1))
# Check if a directory path is provided
if [ -z "$1" ]; then
echo "Usage: $0 [-r] /path/to/directory"
echo " -r Recursively check all git repositories in the directory"
exit 1
fi
BASE_DIR="$1"
if [ ! -d "$BASE_DIR" ]; then
echo "The path '$BASE_DIR' does not exist or is not a directory."
exit 1
fi
# Convert BASE_DIR to absolute path
BASE_DIR="$(cd "$BASE_DIR" && pwd)"
if [ "$RECURSIVE" = true ]; then
# Find and check all git repositories in the directory
while IFS= read -r repo_dir; do
check_repository "$repo_dir"
done < <(find_git_repos "$BASE_DIR")
else
# Check single repository
if [ ! -d "$BASE_DIR/.git" ]; then
echo "The directory '$BASE_DIR' is not a git repository."
exit 1
fi
check_repository "$BASE_DIR"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment