| … | … | @@ -206,22 +206,6 @@ module.exports = class HyperSync extends ReadyResource { |
| 206 | 206 | } |
| 207 | 207 | } |
| 208 | 208 | |
| 209 | | // Detect stuck state: repo has tmp_packs from prior aborted |
| 210 | | // attempts and no refs materialized. Force-reclone by nuking and |
| 211 | | // reinitting — next attempt starts from a clean slate without |
| 212 | | // the user having to rm -rf by hand. |
| 213 | | const wasStuck = this._isStuck(repoPath, packDir); |
| 214 | | if (wasStuck) { |
| 215 | | fs.rmSync(repoPath, { recursive: true, force: true }); |
| 216 | | try { |
| 217 | | await this._exec('git', ['init', '--bare', repoPath]); |
| 218 | | this.emit('pull', remoteName, name, 'force-reclone'); |
| 219 | | } catch (err) { |
| 220 | | result.failed.push({ name, error: 'force-reclone init failed: ' + err.message }); |
| 221 | | continue; |
| 222 | | } |
| 223 | | } |
| 224 | | |
| 225 | 209 | // Pre-fetch cleanup: an interrupted transfer leaves |
| 226 | 210 | // `objects/pack/tmp_pack_XXX` orphaned; these accumulate |
| 227 | 211 | // indefinitely (seen 37 on hyper-overlay) and slow every |
| … | … | @@ -266,8 +250,8 @@ module.exports = class HyperSync extends ReadyResource { |
| 266 | 250 | try { |
| 267 | 251 | this._cleanTmpPacks(packDir); |
| 268 | 252 | await this._gitRefetch(repoPath, fetchUrl); |
| 269 | | // Re-check after refetch. If still broken, fall through |
| 270 | | // to force-reclone below. |
| 253 | // Re-check after refetch. If still broken, report |
| 254 | // failure — don't destroy local state. |
| 271 | 255 | const stillBroken = await this._fsckMissingObjects(repoPath); |
| 272 | 256 | if (stillBroken) synced = false; |
| 273 | 257 | } catch (err) { |
| … | … | @@ -281,28 +265,12 @@ module.exports = class HyperSync extends ReadyResource { |
| 281 | 265 | result.synced.push(name); |
| 282 | 266 | this.emit('pull', remoteName, name, 'complete'); |
| 283 | 267 | } else { |
| 284 | | // Last resort — wipe + fresh clone ONLY when the repo is |
| 285 | | // genuinely stuck (tmp_packs + no refs) AND we haven't |
| 286 | | // already tried the pre-loop force-reclone (wasStuck). Do |
| 287 | | // NOT wipe on generic fetch failure — that can mean network |
| 288 | | // hiccup, peer unreachable, remote down, etc., and wiping |
| 289 | | // would destroy a perfectly-good repo. Previous logic |
| 290 | | // (`|| !wasStuck`) was backwards and nuked healthy repos on |
| 291 | | // any transient fetch error. |
| 292 | | if (!wasStuck && this._isStuck(repoPath, packDir)) { |
| 293 | | fs.rmSync(repoPath, { recursive: true, force: true }); |
| 294 | | try { |
| 295 | | await this._exec('git', ['init', '--bare', repoPath]); |
| 296 | | this.emit('pull', remoteName, name, 'force-reclone'); |
| 297 | | await this._gitFetch(repoPath, fetchUrl); |
| 298 | | synced = true; |
| 299 | | result.synced.push(name); |
| 300 | | this.emit('pull', remoteName, name, 'complete'); |
| 301 | | continue; |
| 302 | | } catch (err) { |
| 303 | | lastErr = err; |
| 304 | | } |
| 305 | | } |
| 268 | // Failure = report it. Don't destroy local state. Git's own |
| 269 | // tools (fetch/fetch --refetch/fsck/repack) have already had |
| 270 | // their shot. If those didn't recover, the caller decides |
| 271 | // what to do — not this code. Previous revisions nuked the |
| 272 | // bare repo under various conditions; that was never the |
| 273 | // plan and has been removed. |
| 306 | 274 | result.failed.push({ name, error: lastErr ? lastErr.message : 'unknown error', attempts: maxAttempts }); |
| 307 | 275 | this.emit('pull', remoteName, name, 'failed'); |
| 308 | 276 | } |
| … | … | @@ -372,26 +340,6 @@ module.exports = class HyperSync extends ReadyResource { |
| 372 | 340 | } catch {} |
| 373 | 341 | } |
| 374 | 342 | |
| 375 | | _isStuck (repoPath, packDir) { |
| 376 | | if (!fs.existsSync(repoPath)) return false; |
| 377 | | // "Stuck" = no refs materialized AND at least one tmp_pack sits |
| 378 | | // in objects/pack/. Either symptom alone might be benign (a fresh |
| 379 | | // init has no refs; a successful fetch has no tmp_packs) — the |
| 380 | | // combination indicates a prior transfer received bytes but never |
| 381 | | // finalized, and plain retry via fetch rarely recovers it. |
| 382 | | let refsCount = 0; |
| 383 | | try { |
| 384 | | refsCount = fs.readdirSync(path.join(repoPath, 'refs', 'heads')).length; |
| 385 | | } catch {} |
| 386 | | if (refsCount > 0) return false; |
| 387 | | try { |
| 388 | | for (const f of fs.readdirSync(packDir)) { |
| 389 | | if (f.startsWith('tmp_pack_')) return true; |
| 390 | | } |
| 391 | | } catch {} |
| 392 | | return false; |
| 393 | | } |
| 394 | | |
| 395 | 343 | // ── Connection Handler ──────────────────────────────────── |
| 396 | 344 | |
| 397 | 345 | _onConnection (conn, info) { |