@@ -56,62 +56,101 @@ sync_repo() { |
| 56 | local org_path="$3" | 56 | local org_path="$3" |
| 57 | local repo_path="${org_path}/${repo}" | 57 | local repo_path="${org_path}/${repo}" |
| 58 | | 58 | |
| 59 | - if [[ -d "$repo_path" ]]; then | 59 | + # Case 1: Valid git repo exists - try to update |
| 60 | - # Repo exists - try to pull | 60 | + if [[ -d "$repo_path/.git" ]]; then |
| 61 | - if [[ -d "$repo_path/.git" ]]; then | 61 | + # Use subshell to avoid cd pollution, capture result |
| 62 | - cd "$repo_path" | 62 | + local result |
| | 63 | + result=$( |
| | 64 | + cd "$repo_path" 2>/dev/null || { echo "invalid"; exit 0; } |
| | 65 | + |
| | 66 | + # Verify it's a valid git repo with a HEAD |
| | 67 | + if ! git rev-parse HEAD &>/dev/null; then |
| | 68 | + echo "invalid" |
| | 69 | + exit 0 |
| | 70 | + fi |
| 63 | | 71 | |
| 64 | - # Check for uncommitted changes | 72 | + # Check for uncommitted changes (staged or unstaged) |
| 65 | - if ! git diff-index --quiet HEAD -- 2>/dev/null; then | 73 | + if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then |
| 66 | - log_warn "$repo (uncommitted changes, skipping)" | 74 | + echo "dirty" |
| 67 | - ((total_skipped++)) || true | 75 | + exit 0 |
| 68 | - return 0 | | |
| 69 | fi | 76 | fi |
| 70 | | 77 | |
| 71 | # Try fast-forward pull | 78 | # Try fast-forward pull |
| 72 | - if output=$(timeout 30 git pull --ff-only 2>&1); then | 79 | + if pull_output=$(timeout 30 git pull --ff-only 2>&1); then |
| 73 | - if [[ "$output" == *"Already up to date"* ]]; then | 80 | + if [[ "$pull_output" == *"Already up to date"* ]]; then |
| 74 | - log_success "$repo (up to date)" | 81 | + echo "uptodate" |
| 75 | else | 82 | else |
| 76 | - log_success "$repo (updated)" | 83 | + echo "updated" |
| 77 | - ((total_updated++)) || true | | |
| 78 | fi | 84 | fi |
| 79 | else | 85 | else |
| | 86 | + echo "diverged" |
| | 87 | + fi |
| | 88 | + ) |
| | 89 | + |
| | 90 | + case "$result" in |
| | 91 | + invalid) |
| | 92 | + log_warn "$repo (invalid git repo, skipping)" |
| | 93 | + ((total_skipped++)) || true |
| | 94 | + ;; |
| | 95 | + dirty) |
| | 96 | + log_warn "$repo (uncommitted changes, skipping)" |
| | 97 | + ((total_skipped++)) || true |
| | 98 | + ;; |
| | 99 | + uptodate) |
| | 100 | + log_success "$repo (up to date)" |
| | 101 | + ;; |
| | 102 | + updated) |
| | 103 | + log_success "$repo (updated)" |
| | 104 | + ((total_updated++)) || true |
| | 105 | + ;; |
| | 106 | + diverged) |
| 80 | log_warn "$repo (can't fast-forward, skipping)" | 107 | log_warn "$repo (can't fast-forward, skipping)" |
| 81 | ((total_skipped++)) || true | 108 | ((total_skipped++)) || true |
| 82 | - fi | 109 | + ;; |
| | 110 | + *) |
| | 111 | + log_warn "$repo (unknown state: $result)" |
| | 112 | + ((total_skipped++)) || true |
| | 113 | + ;; |
| | 114 | + esac |
| | 115 | + return 0 |
| | 116 | + |
| | 117 | + # Case 2: Directory exists but no .git |
| | 118 | + elif [[ -d "$repo_path" ]]; then |
| | 119 | + if [[ -z "$(ls -A "$repo_path" 2>/dev/null)" ]]; then |
| | 120 | + log_info "$repo (empty dir, removing and cloning...)" |
| | 121 | + rmdir "$repo_path" |
| 83 | else | 122 | else |
| 84 | log_warn "$repo (exists but not a git repo)" | 123 | log_warn "$repo (exists but not a git repo)" |
| 85 | ((total_skipped++)) || true | 124 | ((total_skipped++)) || true |
| | 125 | + return 0 |
| | 126 | + fi |
| | 127 | + fi |
| | 128 | + |
| | 129 | + # Case 3: Clone new repo |
| | 130 | + local ssh_url="git@github.com:${github_org}/${repo}.git" |
| | 131 | + log_info "Cloning $repo (SSH)..." |
| | 132 | + if timeout "$CLONE_TIMEOUT" git clone --progress "$ssh_url" "$repo_path" 2>&1; then |
| | 133 | + if [[ -d "$repo_path/.git" ]]; then |
| | 134 | + log_success "$repo (cloned)" |
| | 135 | + ((total_cloned++)) || true |
| | 136 | + else |
| | 137 | + log_error "$repo (clone failed - no permission?)" |
| | 138 | + failed_repos+=("${github_org}/${repo}") |
| | 139 | + ((total_failed++)) || true |
| 86 | fi | 140 | fi |
| 87 | else | 141 | else |
| 88 | - # Clone new repo via SSH with timeout, showing progress | 142 | + local exit_code=$? |
| 89 | - local ssh_url="git@github.com:${github_org}/${repo}.git" | 143 | + if [[ -d "$repo_path/.git" ]]; then |
| 90 | - log_info "Cloning $repo (SSH)..." | 144 | + log_success "$repo (cloned)" |
| 91 | - if timeout "$CLONE_TIMEOUT" git clone --progress "$ssh_url" "$repo_path" 2>&1; then | 145 | + ((total_cloned++)) || true |
| 92 | - if [[ -d "$repo_path/.git" ]]; then | 146 | + elif [[ $exit_code -eq 124 ]]; then |
| 93 | - log_success "$repo (cloned)" | 147 | + log_error "$repo (timeout after ${CLONE_TIMEOUT}s)" |
| 94 | - ((total_cloned++)) || true | 148 | + failed_repos+=("${github_org}/${repo}") |
| 95 | - else | 149 | + ((total_failed++)) || true |
| 96 | - log_error "$repo (clone failed - no permission?)" | | |
| 97 | - failed_repos+=("${github_org}/${repo}") | | |
| 98 | - ((total_failed++)) || true | | |
| 99 | - fi | | |
| 100 | else | 150 | else |
| 101 | - exit_code=$? | 151 | + log_error "$repo (clone failed)" |
| 102 | - # Check if it actually succeeded despite error | 152 | + failed_repos+=("${github_org}/${repo}") |
| 103 | - if [[ -d "$repo_path/.git" ]]; then | 153 | + ((total_failed++)) || true |
| 104 | - log_success "$repo (cloned)" | | |
| 105 | - ((total_cloned++)) || true | | |
| 106 | - elif [[ $exit_code -eq 124 ]]; then | | |
| 107 | - log_error "$repo (timeout after ${CLONE_TIMEOUT}s)" | | |
| 108 | - failed_repos+=("${github_org}/${repo}") | | |
| 109 | - ((total_failed++)) || true | | |
| 110 | - else | | |
| 111 | - log_error "$repo (clone failed)" | | |
| 112 | - failed_repos+=("${github_org}/${repo}") | | |
| 113 | - ((total_failed++)) || true | | |
| 114 | - fi | | |
| 115 | fi | 154 | fi |
| 116 | fi | 155 | fi |
| 117 | } | 156 | } |