#!/bin/bash # Git Submodule Dependency Tree Generator # Usage: ./git-submodule-tree.sh set -euo pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Function to print usage usage() { echo "Usage: $0 " echo "Example: $0 https://github.com/user/repo.git" exit 1 } # Check if URL is provided if [ $# -eq 0 ]; then usage fi REPO_URL="$1" TEMP_DIR=$(mktemp -d) VISITED_REPOS=() # Clean up on exit cleanup() { rm -rf "$TEMP_DIR" } trap cleanup EXIT # Function to check if repo was already visited is_visited() { local repo="$1" for visited in "${VISITED_REPOS[@]}"; do if [ "$visited" == "$repo" ]; then return 0 fi done return 1 } # Function to extract repo info from .gitmodules parse_gitmodules() { local gitmodules_file="$1" local indent="$2" if [ ! -f "$gitmodules_file" ]; then return fi # Parse .gitmodules file while IFS= read -r line; do if [[ $line =~ ^\[submodule[[:space:]]+\"(.+)\"\] ]]; then local name="${BASH_REMATCH[1]}" local path="" local url="" local branch="" # Read submodule details while IFS= read -r subline && ! [[ $subline =~ ^\[submodule ]]; do if [[ $subline =~ path[[:space:]]*=[[:space:]]*(.+) ]]; then path="${BASH_REMATCH[1]}" elif [[ $subline =~ url[[:space:]]*=[[:space:]]*(.+) ]]; then url="${BASH_REMATCH[1]}" elif [[ $subline =~ branch[[:space:]]*=[[:space:]]*(.+) ]]; then branch="${BASH_REMATCH[1]}" fi done if [ -n "$url" ]; then # Print submodule info echo -e "${indent}├── ${GREEN}${name}${NC}" echo -e "${indent}│ └── ${BLUE}${url}${NC}" # Check for commit hash or branch if [ -n "$path" ] && [ -d "$(dirname "$gitmodules_file")/$path" ]; then cd "$(dirname "$gitmodules_file")/$path" 2>/dev/null || true local commit=$(git rev-parse HEAD 2>/dev/null || echo "") if [ -n "$commit" ]; then echo -e "${indent}│ └── ${YELLOW}commit: ${commit:0:8}${NC}" fi if [ -n "$branch" ]; then echo -e "${indent}│ └── ${YELLOW}branch: ${branch}${NC}" fi cd - > /dev/null 2>&1 || true fi # Mark as visited VISITED_REPOS+=("$url") # Recursively process submodule if not already visited if ! is_visited "$url"; then process_repo "$url" "${indent}│ " fi fi fi done < "$gitmodules_file" } # Function to process a repository process_repo() { local repo_url="$1" local indent="${2:-}" # Skip if already visited if is_visited "$repo_url"; then return fi # Create temporary directory for this repo local repo_dir=$(mktemp -d -p "$TEMP_DIR") # Clone the repository (shallow clone for speed) echo -e "${indent}${RED}Cloning: ${repo_url}${NC}" >&2 if ! git clone --depth 1 --recurse-submodules "$repo_url" "$repo_dir" 2>/dev/null; then echo -e "${indent}${RED}Failed to clone: ${repo_url}${NC}" >&2 return fi # Process .gitmodules if it exists if [ -f "$repo_dir/.gitmodules" ]; then parse_gitmodules "$repo_dir/.gitmodules" "$indent" fi # Clean up this repo rm -rf "$repo_dir" } # Main execution echo -e "${GREEN}Git Submodule Dependency Tree${NC}" echo -e "${GREEN}==============================${NC}" echo -e "${BLUE}${REPO_URL}${NC}" # Mark main repo as visited VISITED_REPOS+=("$REPO_URL") # Process the main repository process_repo "$REPO_URL" "" echo -e "\n${GREEN}Complete!${NC}"