ai/ai
1
0
Files
ai/claude/scripts/update-submodules.sh
2025-06-07 03:54:04 +09:00

793 lines
24 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/zsh
# Platform-specific commands
case $OSTYPE in
darwin*)
date_cmd() { gdate "$@"; }
;;
linux*)
date_cmd() { date "$@"; }
;;
esac
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
SCRIPT_DIR="${0:a:h}"
REPO_ROOT="${SCRIPT_DIR:h:h}"
LOGFILE="$REPO_ROOT/.submodule-update.log"
AI_JSON="$REPO_ROOT/ai.json"
# Usage function
usage() {
echo "Usage: $0 [--all] [--module=<name>] [--auto] [--dry-run] [--validate] [--sync] [--add=<name>] [--remove=<name>] [--help]"
echo ""
echo "Options:"
echo " --all Update all submodules"
echo " --module=<name> Update specific submodule (os, gpt, card, etc.)"
echo " --auto Auto-commit if changes detected"
echo " --dry-run Show what would be done without making changes"
echo " --validate Validate URL consistency between ai.json and .gitmodules"
echo " --sync Sync .gitmodules from ai.json configuration"
echo " --add=<name> Add new submodule from ai.json configuration"
echo " --remove=<name> Remove submodule (both from .gitmodules and filesystem)"
echo " --help Show this help message"
echo " --check-consistency Check for project inconsistencies and conflicts"
echo ""
echo "Available submodules:"
git config --file .gitmodules --get-regexp path | awk '{print " " $2}'
exit 1
}
# Logging function
log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date_cmd '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOGFILE"
}
# Parse arguments
UPDATE_ALL=false
SPECIFIC_MODULE=""
AUTO_COMMIT=false
DRY_RUN=false
VALIDATE_ONLY=false
SYNC_GITMODULES=false
ADD_MODULE=""
REMOVE_MODULE=""
CHECK_CONSISTENCY=false
for arg in "$@"; do
case $arg in
--all)
UPDATE_ALL=true
;;
--module=*)
SPECIFIC_MODULE="${arg#*=}"
;;
--auto)
AUTO_COMMIT=true
;;
--dry-run)
DRY_RUN=true
;;
--validate)
VALIDATE_ONLY=true
;;
--sync)
SYNC_GITMODULES=true
;;
--add=*)
ADD_MODULE="${arg#*=}"
;;
--remove=*)
REMOVE_MODULE="${arg#*=}"
;;
--check-consistency)
CHECK_CONSISTENCY=true
;;
--help|-h)
usage
;;
*)
echo "Unknown argument: $arg"
usage
;;
esac
done
# Validate arguments
operations_count=0
[[ "$UPDATE_ALL" == true ]] && ((operations_count++))
[[ -n "$SPECIFIC_MODULE" ]] && ((operations_count++))
[[ "$VALIDATE_ONLY" == true ]] && ((operations_count++))
[[ "$SYNC_GITMODULES" == true ]] && ((operations_count++))
[[ -n "$ADD_MODULE" ]] && ((operations_count++))
[[ -n "$REMOVE_MODULE" ]] && ((operations_count++))
[[ "$CHECK_CONSISTENCY" == true ]] && ((operations_count++))
if [[ $operations_count -eq 0 ]]; then
echo "Error: At least one operation is required"
usage
fi
if [[ $operations_count -gt 1 ]]; then
echo "Error: Only one operation can be specified at a time"
usage
fi
# Change to repository root
cd "$REPO_ROOT" || exit 1
echo -e "${BLUE}🚀 Starting submodule update...${NC}"
log "INFO" "Starting submodule update (all=$UPDATE_ALL, module=$SPECIFIC_MODULE, auto=$AUTO_COMMIT, dry-run=$DRY_RUN)"
if [[ "$DRY_RUN" == true ]]; then
echo -e "${YELLOW}🔍 DRY RUN MODE - No changes will be made${NC}"
fi
# Update function
update_submodule() {
local module_name="$1"
local module_path="$2"
echo -e "\n${BLUE}📦 Processing: $module_name${NC}"
# Check if submodule exists
if [[ ! -d "$module_path" ]]; then
echo -e "${RED}❌ Submodule directory not found: $module_path${NC}"
return 1
fi
# Get current commit
local current_commit=$(git submodule status "$module_path" | awk '{print $1}' | sed 's/^[+-]//')
# Get current branch of the submodule
cd "$module_path" || return 1
local current_branch=$(git branch --show-current)
local target_branch="${branches[$module_name]:-main}"
if [[ -z "$current_branch" ]]; then
# If not on a branch (detached HEAD), use target branch from ai.json
current_branch="$target_branch"
fi
cd "$REPO_ROOT"
if [[ "$DRY_RUN" == true ]]; then
echo -e "${YELLOW}🔍 [DRY RUN] Would update $module_name (target branch: $target_branch)${NC}"
echo " Current: $current_commit"
cd "$module_path" || return 1
git fetch origin >/dev/null 2>&1
local latest_commit=$(git rev-parse "origin/$target_branch" 2>/dev/null)
echo " Latest: $latest_commit"
cd "$REPO_ROOT"
if [[ "$current_commit" != "$latest_commit" ]]; then
echo -e "${YELLOW} 📝 Changes available${NC}"
return 0
else
echo -e "${GREEN} ✅ Already up to date${NC}"
return 1
fi
fi
# Update submodule
echo "🔄 Fetching latest changes..."
cd "$module_path" || return 1
if ! git fetch origin; then
echo -e "${RED}❌ Failed to fetch from origin${NC}"
cd "$REPO_ROOT"
return 1
fi
# Use the target branch from ai.json
local latest_commit=$(git rev-parse "origin/$target_branch" 2>/dev/null)
if [[ "$current_commit" == "$latest_commit" ]]; then
echo -e "${GREEN}✅ Already up to date${NC}"
cd "$REPO_ROOT"
return 1
fi
echo "📝 Updating to latest commit on branch $target_branch (configured in ai.json)..."
# First ensure we're on the correct branch
if ! git checkout "$target_branch"; then
echo -e "${RED}❌ Failed to checkout branch $target_branch${NC}"
cd "$REPO_ROOT"
return 1
fi
# Then pull the latest changes
if ! git pull origin "$target_branch"; then
echo -e "${RED}❌ Failed to pull latest changes from $target_branch${NC}"
cd "$REPO_ROOT"
return 1
fi
cd "$REPO_ROOT"
# Get the new commit after update
local new_commit=$(cd "$module_path" && git rev-parse HEAD)
# Stage the submodule update
git add "$module_path"
echo -e "${GREEN}✅ Updated $module_name (branch: $target_branch)${NC}"
echo " From: $current_commit"
echo " To: $new_commit"
log "INFO" "Updated $module_name on branch $target_branch: $current_commit -> $new_commit"
return 0
}
# Get list of submodules
declare -A submodules
while IFS= read -r line; do
if [[ $line =~ '^\[submodule "([^"]+)"\]' ]]; then
current_name="${match[1]}"
elif [[ $line =~ '^[[:space:]]*path[[:space:]]*=[[:space:]]*(.+)$' ]]; then
submodules[$current_name]="${match[1]}"
fi
done < .gitmodules
# Get branch information from ai.json
declare -A branches
get_branch_for_module() {
local module="$1"
local branch="main" # default branch
if [[ -f "$AI_JSON" ]]; then
# Try to extract branch from ai.json using jq
if command -v jq >/dev/null 2>&1; then
local json_branch=$(jq -r ".ai.${module}.branch // \"main\"" "$AI_JSON" 2>/dev/null)
if [[ -n "$json_branch" && "$json_branch" != "null" ]]; then
branch="$json_branch"
fi
fi
fi
echo "$branch"
}
# Get URL and branch information from ai.json
declare -A urls
get_git_config() {
local config_key="$1"
local default_value="$2"
local value=""
if [[ -f "$AI_JSON" ]]; then
if command -v jq >/dev/null 2>&1; then
value=$(jq -r ".metadata.git.${config_key} // \"${default_value}\"" "$AI_JSON" 2>/dev/null)
fi
fi
# Fallback to default if not found
if [[ -z "$value" || "$value" == "null" ]]; then
value="$default_value"
fi
echo "$value"
}
# Extract username and repo from JSON path
# For ai.gpt -> username=ai, repo=gpt
extract_git_info_from_path() {
local module="$1"
local username=""
local repo=""
# Check if module exists in ai.json structure
if [[ -f "$AI_JSON" ]]; then
if command -v jq >/dev/null 2>&1; then
# Get all keys in the ai object
local ai_keys=$(jq -r '.ai | keys[]' "$AI_JSON" 2>/dev/null)
# Find the first level (username) that contains our module
while IFS= read -r key; do
if jq -e ".ai.${key}.${module}" "$AI_JSON" >/dev/null 2>&1; then
username="$key"
repo="$module"
break
fi
# Also check if the key itself is our module (direct under ai)
if [[ "$key" == "$module" ]]; then
username="ai" # Default namespace
repo="$module"
break
fi
done <<< "$ai_keys"
fi
fi
# Fallback: assume ai namespace
if [[ -z "$username" || -z "$repo" ]]; then
username="ai"
repo="$module"
fi
echo "${username}:${repo}"
}
get_base_url_for_module() {
local module="$1"
local host=$(get_git_config "host" "git.syui.ai")
local protocol=$(get_git_config "protocol" "ssh")
# Extract username and repo from JSON structure
local git_info=$(extract_git_info_from_path "$module")
local username="${git_info%%:*}"
local repo="${git_info##*:}"
local url=""
case "$protocol" in
"ssh")
url="git@${host}:${username}/${repo}"
;;
"https")
url="https://${host}/${username}/${repo}"
;;
*)
# Default to ssh
url="git@${host}:${username}/${repo}"
;;
esac
echo "$url"
}
get_url_for_module() {
local module="$1"
local url=$(get_base_url_for_module "$module")
# Check if there's a custom git_url override in ai.json
if [[ -f "$AI_JSON" ]]; then
if command -v jq >/dev/null 2>&1; then
local custom_url=$(jq -r ".ai.${module}.git_url // empty" "$AI_JSON" 2>/dev/null)
if [[ -n "$custom_url" && "$custom_url" != "null" ]]; then
url="$custom_url"
fi
fi
fi
echo "$url"
}
# Populate branches and URLs for all modules
for module in "${(k)submodules[@]}"; do
branches[$module]=$(get_branch_for_module "$module")
urls[$module]=$(get_url_for_module "$module")
done
# Log git configuration for debugging
log "INFO" "Git host: $(get_git_config 'host' 'git.syui.ai'), protocol: $(get_git_config 'protocol' 'ssh')"
# Debug: Log found submodules
log "INFO" "Found ${#submodules} submodules: ${(k)submodules[@]}"
# Validation function
validate_url_consistency() {
echo -e "${BLUE}🔍 Validating URL consistency between ai.json and .gitmodules...${NC}"
local inconsistencies=0
for module in "${(k)submodules[@]}"; do
local gitmodules_url=$(git config --file .gitmodules --get "submodule.${module}.url")
local ai_json_url="${urls[$module]}"
if [[ -n "$ai_json_url" ]]; then
if [[ "$gitmodules_url" != "$ai_json_url" ]]; then
echo -e "${RED}$module: URL mismatch${NC}"
echo " .gitmodules: $gitmodules_url"
echo " ai.json: $ai_json_url"
((inconsistencies++))
else
echo -e "${GREEN}$module: URLs match${NC}"
fi
else
echo -e "${YELLOW}⚠️ $module: No git_url in ai.json${NC}"
fi
done
echo ""
if [[ $inconsistencies -eq 0 ]]; then
echo -e "${GREEN}🎉 All URLs are consistent!${NC}"
log "INFO" "URL validation passed: all URLs consistent"
else
echo -e "${RED}❌ Found $inconsistencies URL inconsistencies${NC}"
echo "Run with --sync to fix inconsistencies"
log "ERROR" "URL validation failed: $inconsistencies inconsistencies"
return 1
fi
}
# Sync .gitmodules from ai.json
sync_gitmodules() {
echo -e "${BLUE}🔄 Syncing .gitmodules from ai.json...${NC}"
local changes=0
for module in "${(k)submodules[@]}"; do
local ai_json_url="${urls[$module]}"
local ai_json_branch="${branches[$module]}"
if [[ -n "$ai_json_url" ]]; then
local current_url=$(git config --file .gitmodules --get "submodule.${module}.url")
if [[ "$current_url" != "$ai_json_url" ]]; then
echo "📝 Updating $module URL: $current_url -> $ai_json_url"
git config --file .gitmodules "submodule.${module}.url" "$ai_json_url"
((changes++))
fi
fi
done
if [[ $changes -gt 0 ]]; then
echo -e "${GREEN}✅ Updated $changes URL(s) in .gitmodules${NC}"
log "INFO" "Synced .gitmodules: updated $changes URLs"
if [[ "$AUTO_COMMIT" == true ]]; then
git add .gitmodules
git commit -m "Sync .gitmodules URLs from ai.json
🔄 Updated $changes submodule URL(s)
🤖 Generated with submodule sync"
echo -e "${GREEN}✅ Changes committed to .gitmodules${NC}"
fi
else
echo -e "${GREEN}✅ .gitmodules is already in sync${NC}"
fi
}
# Add new submodule
add_submodule() {
local module="$1"
echo -e "${BLUE} Adding submodule: $module${NC}"
local ai_json_url=$(get_url_for_module "$module")
local ai_json_branch=$(get_branch_for_module "$module")
if [[ -z "$ai_json_url" ]]; then
echo -e "${RED}❌ No git_url found for '$module' in ai.json${NC}"
return 1
fi
if [[ -d "$module" ]]; then
echo -e "${RED}❌ Directory '$module' already exists${NC}"
return 1
fi
echo "📦 Adding submodule $module"
echo " URL: $ai_json_url"
echo " Branch: $ai_json_branch"
if [[ "$DRY_RUN" == true ]]; then
echo -e "${YELLOW}🔍 [DRY RUN] Would add submodule${NC}"
return 0
fi
if git submodule add -b "$ai_json_branch" "$ai_json_url" "$module"; then
echo -e "${GREEN}✅ Successfully added submodule $module${NC}"
log "INFO" "Added submodule $module from $ai_json_url (branch: $ai_json_branch)"
if [[ "$AUTO_COMMIT" == true ]]; then
git commit -m "Add submodule: $module
📦 Added from ai.json configuration
🌐 URL: $ai_json_url
🌿 Branch: $ai_json_branch
🤖 Generated with submodule manager"
echo -e "${GREEN}✅ Submodule addition committed${NC}"
fi
else
echo -e "${RED}❌ Failed to add submodule $module${NC}"
return 1
fi
}
# Remove submodule
remove_submodule() {
local module="$1"
echo -e "${BLUE} Removing submodule: $module${NC}"
if [[ ! -d "$module" ]]; then
echo -e "${RED}❌ Submodule '$module' does not exist${NC}"
return 1
fi
if [[ "$DRY_RUN" == true ]]; then
echo -e "${YELLOW}🔍 [DRY RUN] Would remove submodule${NC}"
return 0
fi
# Remove from .gitmodules
git config --file .gitmodules --remove-section "submodule.$module" 2>/dev/null || true
# Remove from .git/config
git config --remove-section "submodule.$module" 2>/dev/null || true
# Remove from git index
git rm --cached "$module" 2>/dev/null || true
# Remove directory
rm -rf "$module"
# Remove from .git/modules
rm -rf ".git/modules/$module"
echo -e "${GREEN}✅ Successfully removed submodule $module${NC}"
log "INFO" "Removed submodule $module"
if [[ "$AUTO_COMMIT" == true ]]; then
git add .gitmodules "$module" 2>/dev/null || true
git commit -m "Remove submodule: $module
🗑️ Completely removed submodule
🤖 Generated with submodule manager"
echo -e "${GREEN}✅ Submodule removal committed${NC}"
fi
}
# Consistency check function
check_project_consistency() {
echo -e "${BLUE}🔍 Checking project consistency...${NC}"
local issues=0
local warnings=0
# Check for uncommitted changes in submodules
echo -e "${BLUE}📝 Checking for uncommitted changes...${NC}"
for module in "${(k)submodules[@]}"; do
local module_path="${submodules[$module]}"
if [[ -d "$module_path" ]]; then
cd "$module_path"
if ! git diff --quiet || ! git diff --cached --quiet; then
echo -e "${YELLOW}⚠️ $module: Has uncommitted changes${NC}"
git status --short | sed 's/^/ /'
((warnings++))
else
echo -e "${GREEN}$module: Clean working directory${NC}"
fi
cd "$REPO_ROOT"
fi
done
# Check for branch inconsistencies
echo -e "${BLUE}🌿 Checking branch consistency...${NC}"
for module in "${(k)submodules[@]}"; do
local module_path="${submodules[$module]}"
local expected_branch="${branches[$module]}"
if [[ -d "$module_path" ]]; then
cd "$module_path"
local current_branch=$(git branch --show-current)
if [[ -z "$current_branch" ]]; then
current_branch="(detached HEAD)"
fi
if [[ "$current_branch" != "$expected_branch" ]]; then
echo -e "${YELLOW}⚠️ $module: Branch mismatch${NC}"
echo " Current: $current_branch"
echo " Expected: $expected_branch"
((warnings++))
else
echo -e "${GREEN}$module: Correct branch ($current_branch)${NC}"
fi
cd "$REPO_ROOT"
fi
done
# Check for remote synchronization
echo -e "${BLUE}🌐 Checking remote synchronization...${NC}"
for module in "${(k)submodules[@]}"; do
local module_path="${submodules[$module]}"
if [[ -d "$module_path" ]]; then
cd "$module_path"
git fetch --quiet 2>/dev/null || true
local ahead=$(git rev-list --count HEAD ^origin/HEAD 2>/dev/null || echo "0")
local behind=$(git rev-list --count origin/HEAD ^HEAD 2>/dev/null || echo "0")
if [[ "$ahead" -gt 0 || "$behind" -gt 0 ]]; then
echo -e "${YELLOW}⚠️ $module: Out of sync with remote${NC}"
if [[ "$ahead" -gt 0 ]]; then
echo " $ahead commits ahead"
fi
if [[ "$behind" -gt 0 ]]; then
echo " $behind commits behind"
fi
((warnings++))
else
echo -e "${GREEN}$module: Synchronized with remote${NC}"
fi
cd "$REPO_ROOT"
fi
done
# Check parent repository status
echo -e "${BLUE}🏠 Checking parent repository...${NC}"
if ! git diff --quiet || ! git diff --cached --quiet; then
echo -e "${YELLOW}⚠️ Parent repository has uncommitted changes${NC}"
git status --short | sed 's/^/ /'
((warnings++))
else
echo -e "${GREEN}✅ Parent repository is clean${NC}"
fi
# Check for submodule pointer mismatches
echo -e "${BLUE}🔗 Checking submodule pointers...${NC}"
for module in "${(k)submodules[@]}"; do
local module_path="${submodules[$module]}"
if [[ -d "$module_path" ]]; then
local submodule_commit=$(git ls-tree HEAD "$module_path" | awk '{print $3}')
local actual_commit=$(cd "$module_path" && git rev-parse HEAD)
if [[ "$submodule_commit" != "$actual_commit" ]]; then
echo -e "${YELLOW}⚠️ $module: Submodule pointer mismatch${NC}"
echo " Pointer: $submodule_commit"
echo " Actual: $actual_commit"
((warnings++))
else
echo -e "${GREEN}$module: Submodule pointer correct${NC}"
fi
fi
done
# Summary
echo ""
echo -e "${BLUE}📊 Consistency Check Summary:${NC}"
echo " 📦 Modules checked: ${#submodules}"
if [[ $issues -eq 0 && $warnings -eq 0 ]]; then
echo -e "${GREEN} ✅ No issues found${NC}"
echo -e "${GREEN}🎉 All projects are consistent!${NC}"
log "INFO" "Consistency check passed: no issues found"
return 0
else
if [[ $issues -gt 0 ]]; then
echo -e "${RED} ❌ Issues: $issues${NC}"
fi
if [[ $warnings -gt 0 ]]; then
echo -e "${YELLOW} ⚠️ Warnings: $warnings${NC}"
fi
echo ""
echo -e "${BLUE}💡 Recommended actions:${NC}"
echo " • Commit uncommitted changes in affected modules"
echo " • Switch to expected branches where needed"
echo " • Run: ./claude/scripts/update-submodules.sh --all --auto"
echo " • Use session-end.sh for proper session cleanup"
log "WARNING" "Consistency check found $issues issues and $warnings warnings"
return 1
fi
}
# Handle special operations
if [[ "$VALIDATE_ONLY" == true ]]; then
validate_url_consistency
exit $?
fi
if [[ "$SYNC_GITMODULES" == true ]]; then
sync_gitmodules
exit 0
fi
if [[ -n "$ADD_MODULE" ]]; then
add_submodule "$ADD_MODULE"
exit $?
fi
if [[ -n "$REMOVE_MODULE" ]]; then
remove_submodule "$REMOVE_MODULE"
exit $?
fi
if [[ "$CHECK_CONSISTENCY" == true ]]; then
check_project_consistency
exit $?
fi
# Main execution
success_count=0
total_count=0
if [[ "$UPDATE_ALL" == true ]]; then
echo -e "${BLUE}📚 Updating all submodules...${NC}"
for module_name in "${(k)submodules[@]}"; do
module_path="${submodules[$module_name]}"
((total_count++))
if update_submodule "$module_name" "$module_path"; then
((success_count++))
fi
done
else
echo -e "${BLUE}📖 Updating submodule: $SPECIFIC_MODULE${NC}"
if [[ -z "${submodules[$SPECIFIC_MODULE]}" ]]; then
echo -e "${RED}❌ Submodule '$SPECIFIC_MODULE' not found${NC}"
echo "Available submodules:"
for name in "${(k)submodules[@]}"; do
echo " - $name (path: ${submodules[$name]})"
done
exit 1
fi
module_path="${submodules[$SPECIFIC_MODULE]}"
((total_count++))
if update_submodule "$SPECIFIC_MODULE" "$module_path"; then
((success_count++))
fi
fi
# Summary
echo ""
echo -e "${BLUE}📊 Summary:${NC}"
echo " 📦 Modules processed: $total_count"
echo " ✅ Updates applied: $success_count"
echo " 📝 No changes: $((total_count - success_count))"
if [[ "$DRY_RUN" == true ]]; then
echo ""
echo -e "${YELLOW}🔍 This was a dry run. To apply changes, run without --dry-run${NC}"
exit 0
fi
# Auto-commit if requested and changes exist
if [[ "$success_count" -gt 0 ]]; then
if [[ "$AUTO_COMMIT" == true ]]; then
echo ""
echo -e "${BLUE}💾 Auto-committing submodule updates...${NC}"
commit_message="Update submodules
📦 Updated modules: $success_count/$total_count
$(git diff --cached --name-only | sed 's/^/- /')
🤖 Generated with submodule auto-update
$(date_cmd '+%Y-%m-%d %H:%M:%S')"
if git commit -m "$commit_message"; then
echo -e "${GREEN}✅ Changes committed successfully${NC}"
log "INFO" "Auto-committed $success_count submodule updates"
else
echo -e "${RED}❌ Failed to commit changes${NC}"
log "ERROR" "Failed to auto-commit submodule updates"
fi
else
echo ""
echo -e "${YELLOW}📝 Changes staged but not committed. Run 'git commit' to commit them.${NC}"
echo "Or use --auto flag to commit automatically."
fi
fi
echo ""
echo -e "${GREEN}🎉 Submodule update completed!${NC}"
log "INFO" "Submodule update completed: $success_count/$total_count updated"