Last active
December 10, 2024 10:14
-
-
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
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 | |
# 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