#!/bin/bash # ClaudeTools Bidirectional Sync Script # Ensures proper pull BEFORE push on all machines # Prints incoming/outgoing change summary with author attribution set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # Machine + timestamp if [ -n "$COMPUTERNAME" ]; then MACHINE="$COMPUTERNAME" else MACHINE=$(hostname) fi TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S") echo -e "${GREEN}[OK]${NC} Starting ClaudeTools sync from $MACHINE at $TIMESTAMP" # Navigate to ClaudeTools directory (check common locations) for candidate in "$HOME/ClaudeTools" "/d/ClaudeTools" "D:/ClaudeTools" "/d/claudetools" "D:/claudetools"; do if [ -d "$candidate" ]; then cd "$candidate" break fi done if [ ! -d ".git" ]; then echo -e "${RED}[ERROR]${NC} Not in a git working tree" exit 1 fi echo -e "${GREEN}[OK]${NC} Working directory: $(pwd)" # Load user identity USER_DISPLAY="unknown" USER_GITEA="" if [ -f ".claude/identity.json" ]; then USER_DISPLAY=$(python -c "import json,sys; d=json.load(open('.claude/identity.json')); print(d.get('full_name', d.get('user','unknown')))" 2>/dev/null || echo "unknown") USER_GITEA=$(python -c "import json,sys; d=json.load(open('.claude/identity.json')); print(d.get('user',''))" 2>/dev/null || echo "") fi echo -e "${GREEN}[OK]${NC} Syncing as: $USER_DISPLAY (machine: $MACHINE)" # Phase 1: Local changes echo "" echo "=== Phase 1: Local changes ===" if ! git diff-index --quiet HEAD -- 2>/dev/null; then echo -e "${YELLOW}[INFO]${NC} Local changes detected:" git status --short echo "" echo -e "${GREEN}[OK]${NC} Staging all changes..." git add -A # Commit message (Co-Authored-By uses local git user if configured) COMMIT_MSG="sync: auto-sync from $MACHINE at $TIMESTAMP Author: $USER_DISPLAY Machine: $MACHINE Timestamp: $TIMESTAMP" git commit -m "$COMMIT_MSG" echo -e "${GREEN}[OK]${NC} Committed." else echo -e "${GREEN}[OK]${NC} No local changes to commit." fi # Phase 2: Remote sync echo "" echo "=== Phase 2: Fetch + inspect ===" LOCAL_BEFORE=$(git rev-parse HEAD) echo -e "${GREEN}[OK]${NC} Fetching from origin..." git fetch origin --quiet LOCAL=$(git rev-parse HEAD) REMOTE=$(git rev-parse origin/main 2>/dev/null || git rev-parse origin/master 2>/dev/null || echo "$LOCAL") REMOTE_BRANCH="origin/main" if ! git rev-parse origin/main >/dev/null 2>&1; then REMOTE_BRANCH="origin/master" fi # Count and show incoming INCOMING_COUNT=$(git rev-list --count HEAD..$REMOTE_BRANCH 2>/dev/null || echo 0) OUTGOING_COUNT=$(git rev-list --count $REMOTE_BRANCH..HEAD 2>/dev/null || echo 0) if [ "$INCOMING_COUNT" -gt 0 ]; then echo "" echo -e "${CYAN}--- Incoming: $INCOMING_COUNT commits from remote ---${NC}" git log --oneline --format=' %C(yellow)%h%Creset %C(cyan)%an%Creset %s %C(dim)(%ar)%Creset' HEAD..$REMOTE_BRANCH | head -30 echo "" echo -e "${CYAN}--- Files touched by incoming commits ---${NC}" git diff --stat HEAD..$REMOTE_BRANCH | tail -20 else echo -e "${GREEN}[OK]${NC} No incoming changes." fi if [ "$OUTGOING_COUNT" -gt 0 ]; then echo "" echo -e "${CYAN}--- Outgoing: $OUTGOING_COUNT commits to remote ---${NC}" git log --oneline --format=' %C(yellow)%h%Creset %C(cyan)%an%Creset %s %C(dim)(%ar)%Creset' $REMOTE_BRANCH..HEAD | head -30 fi # Phase 3: Pull (if needed) if [ "$INCOMING_COUNT" -gt 0 ]; then echo "" echo "=== Phase 3: Pull (rebase) ===" if git pull origin main --rebase; then echo -e "${GREEN}[OK]${NC} Pulled successfully." else echo -e "${RED}[ERROR]${NC} Pull failed (likely conflicts). Resolve and re-run sync." exit 1 fi fi # Phase 4: Push (if needed) OUTGOING_AFTER_PULL=$(git rev-list --count $REMOTE_BRANCH..HEAD 2>/dev/null || echo 0) if [ "$OUTGOING_AFTER_PULL" -gt 0 ]; then echo "" echo "=== Phase 4: Push ===" if git push origin main; then echo -e "${GREEN}[OK]${NC} Pushed successfully." else echo -e "${RED}[ERROR]${NC} Push failed. Check auth / network." exit 1 fi else echo -e "${GREEN}[OK]${NC} Nothing to push." fi # Phase 5: Summary echo "" echo "=== Sync Summary ===" if [ "$INCOMING_COUNT" -gt 0 ]; then # Count commits by author INCOMING_AUTHORS=$(git log --format='%an' $LOCAL_BEFORE..HEAD 2>/dev/null | sort | uniq -c | sort -rn | awk '{printf "%s (%s), ", substr($0, index($0,$2)), $1}' | sed 's/, $//') echo -e "${CYAN}Pulled in:${NC} $INCOMING_COUNT commit(s) — authors: ${INCOMING_AUTHORS:-unknown}" fi if [ "$OUTGOING_AFTER_PULL" -gt 0 ]; then echo -e "${CYAN}Pushed out:${NC} $OUTGOING_AFTER_PULL commit(s) by $USER_DISPLAY" fi if [ "$INCOMING_COUNT" -eq 0 ] && [ "$OUTGOING_AFTER_PULL" -eq 0 ]; then echo -e "${GREEN}Already in sync — no commits moved in either direction.${NC}" fi echo -e "${GREEN}[OK]${NC} HEAD: $(git log -1 --oneline)" echo -e "${GREEN}[OK]${NC} Status: $(git status -sb | head -1)" echo "" echo -e "${GREEN}[SUCCESS]${NC} Sync complete."