OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-sync commits / a95b200

pull: remove wipe entirely — git's native tools only
User's explicit direction: the plan was never to wipe. Git has its own primitives (fetch, fetch --refetch, fsck, repack) for recovery from off-state repos. Destroying local state was never part of the plan — that was my invention that crept in. Removed: - Pre-loop `wasStuck` force-reclone (rm -rf + git init when repo looked stuck). Replaced with: nothing. If the repo has stale tmp_packs, the existing _cleanTmpPacks call handles them; if it has more severe issues, the fsck → --refetch tier addresses that after the fetch attempt. - Post-loop force-reclone on failure. Replaced with: report failure. Caller decides what to do. - _isStuck helper (dead code after removing both wipe callsites). Fetch failure now means "report and move on" — NOT "destroy the bare repo." Every previous wipe path either nuked healthy repos on transient errors or introduced latent bugs. They're all gone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a95b2003cc9fb1158243b2d24c5e06fadfe13585
by plankatron <fleeky@gmail.com> · 2026-04-22T01:48:45.000Z

Changed files (1) +8 -60

index.js +8 -60
……@@ -206,22 +206,6 @@ module.exports = class HyperSync extends ReadyResource {
206206 }
207207 }
208208
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
225209 // Pre-fetch cleanup: an interrupted transfer leaves
226210 // `objects/pack/tmp_pack_XXX` orphaned; these accumulate
227211 // indefinitely (seen 37 on hyper-overlay) and slow every
……@@ -266,8 +250,8 @@ module.exports = class HyperSync extends ReadyResource {
266250 try {
267251 this._cleanTmpPacks(packDir);
268252 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.
271255 const stillBroken = await this._fsckMissingObjects(repoPath);
272256 if (stillBroken) synced = false;
273257 } catch (err) {
……@@ -281,28 +265,12 @@ module.exports = class HyperSync extends ReadyResource {
281265 result.synced.push(name);
282266 this.emit('pull', remoteName, name, 'complete');
283267 } 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.
306274 result.failed.push({ name, error: lastErr ? lastErr.message : 'unknown error', attempts: maxAttempts });
307275 this.emit('pull', remoteName, name, 'failed');
308276 }
……@@ -372,26 +340,6 @@ module.exports = class HyperSync extends ReadyResource {
372340 } catch {}
373341 }
374342
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
395343 // ── Connection Handler ────────────────────────────────────
396344
397345 _onConnection (conn, info) {