| … | … | @@ -63,7 +63,7 @@ export class GitSyncClientManager { |
| 63 | 63 | * @param {object} remoteRefs — remote ref map { refName: sha, ... } |
| 64 | 64 | * @param {string} peerKey — hex public key of the peer that sent the update |
| 65 | 65 | */ |
| 66 | | async handleRefUpdate (repoName, remoteRefs, peerKey) { |
| 66 | async handleRefUpdate (repoName, remoteRefs, peerKey, syncServer) { |
| 67 | 67 | if (this._destroyed) return |
| 68 | 68 | if (this.serverKey && peerKey === this.serverKey) return // self-sync guard |
| 69 | 69 | |
| … | … | @@ -106,7 +106,7 @@ export class GitSyncClientManager { |
| 106 | 106 | if (diff.removed.length > 0) summary.push(`${diff.removed.length} removed`) |
| 107 | 107 | console.log(`[GitSyncClient] Syncing ${repoName} from ${peerKey.slice(0, 8)} (${summary.join(', ')})`) |
| 108 | 108 | |
| 109 | | const syncPromise = this._syncRepo(repoName, repoPath, peerKey) |
| 109 | const syncPromise = this._syncRepo(repoName, repoPath, peerKey, syncServer) |
| 110 | 110 | .then(() => { |
| 111 | 111 | console.log(`[GitSyncClient] Synced ${repoName} from ${peerKey.slice(0, 8)}`) |
| 112 | 112 | }) |
| … | … | @@ -313,41 +313,38 @@ export class GitSyncClientManager { |
| 313 | 313 | * @param {string} peerKey — hex public key of the peer to fetch from |
| 314 | 314 | * @returns {Promise<void>} |
| 315 | 315 | */ |
| 316 | | async _syncRepo (repoName, repoPath, peerKey) { |
| 316 | async _syncRepo (repoName, repoPath, peerKey, syncServer) { |
| 317 | 317 | const fetchUrl = `punch://${peerKey}/${repoName}` |
| 318 | 318 | console.log(`[GitSync] Fetching ${repoName} from ${peerKey.slice(0, 8)}...`) |
| 319 | 319 | |
| 320 | 320 | await new Promise((resolve, reject) => { |
| 321 | 321 | let done = false |
| 322 | | // detached: true creates a new process group so we can kill all children |
| 323 | 322 | const child = spawn('git', [ |
| 324 | 323 | '-C', repoPath, 'fetch', fetchUrl, |
| 325 | | '+refs/*:refs/*', '--no-tags', '--prune' |
| 324 | 'refs/*:refs/*', '--no-tags' |
| 326 | 325 | ], { detached: true }) |
| 327 | 326 | |
| 328 | | const timer = setTimeout(() => { |
| 329 | | if (done) return |
| 330 | | done = true |
| 331 | | // Kill the entire process group (git + git-remote-punch grandchild) |
| 332 | | try { process.kill(-child.pid, 'SIGKILL') } catch {} |
| 333 | | reject(new Error(`git fetch timeout after 120s for ${repoName}`)) |
| 334 | | }, 120000) |
| 335 | | |
| 336 | | let stderr = '' |
| 337 | | child.stderr.on('data', (d) => { stderr += d.toString() }) |
| 327 | // No hard timeout — large repos (1+ GB) can take minutes. |
| 328 | // The P2P transport (SecretStream) has its own inactivity timeout |
| 329 | // (45s) which handles hung connections. We just need to wait for |
| 330 | // the transfer to complete, however long it takes. |
| 338 | 331 | child.on('exit', (code) => { |
| 339 | 332 | if (done) return |
| 340 | 333 | done = true |
| 341 | | clearTimeout(timer) |
| 342 | 334 | if (code === 0) resolve() |
| 343 | | else reject(new Error(`git fetch exit ${code}: ${stderr.trim()}`)) |
| 335 | else { |
| 336 | const stderr = stderrBuf.join('') |
| 337 | reject(new Error(`git fetch exit ${code}: ${stderr.trim()}`)) |
| 338 | } |
| 344 | 339 | }) |
| 345 | 340 | child.on('error', (err) => { |
| 346 | 341 | if (done) return |
| 347 | 342 | done = true |
| 348 | | clearTimeout(timer) |
| 349 | 343 | reject(err) |
| 350 | 344 | }) |
| 345 | |
| 346 | const stderrBuf = [] |
| 347 | child.stderr.on('data', (d) => { stderrBuf.push(d.toString()) }) |
| 351 | 348 | }) |
| 352 | 349 | |
| 353 | 350 | // Refresh repo metadata (commits, branches, lastCommit) so the UI stays current |