| … | … | @@ -5,6 +5,7 @@ const crypto = require('hypercore-crypto'); |
| 5 | 5 | const fs = require('fs'); |
| 6 | 6 | const path = require('path'); |
| 7 | 7 | const { spawn } = require('child_process'); |
| 8 | const process = require('process'); |
| 8 | 9 | |
| 9 | 10 | const { scanRepos, repoInfo, readRefs } = require('./lib/repos'); |
| 10 | 11 | const { deriveTopic } = require('./lib/topics'); |
| … | … | @@ -327,7 +328,7 @@ module.exports = class HyperSync extends ReadyResource { |
| 327 | 328 | return new Promise((resolve, reject) => { |
| 328 | 329 | const proc = spawn('git', [ |
| 329 | 330 | '-C', repoPath, 'fetch', '--refetch', fetchUrl, '+refs/*:refs/*', '--no-tags' |
| 330 | | ], { stdio: ['ignore', 'pipe', 'pipe'] }); |
| 331 | ], { stdio: ['ignore', 'pipe', 'pipe'], env: this._gitEnv() }); |
| 331 | 332 | const errChunks = []; |
| 332 | 333 | proc.stderr.on('data', (d) => errChunks.push(d)); |
| 333 | 334 | proc.on('exit', (code) => { |
| … | … | @@ -485,6 +486,61 @@ module.exports = class HyperSync extends ReadyResource { |
| 485 | 486 | |
| 486 | 487 | // ── Helpers ─────────────────────────────────────────────── |
| 487 | 488 | |
| 489 | // git-remote-punch is the remote helper that makes `git fetch punch://...` |
| 490 | // work — a SEPARATE bare app, not something git ships. Rather than rely on it |
| 491 | // being hand-installed on every peer's PATH (it usually isn't, and is |
| 492 | // off-PATH even on machines that have it), self-provision: locate our bundled |
| 493 | // git-remote-punch + the running bare, write a tiny shim into a helper dir, |
| 494 | // and prepend that dir to the PATH of the `git` child we spawn. Idempotent + |
| 495 | // cached; returns the helper dir, or null when we can't provision (git then |
| 496 | // falls back to whatever `git-remote-punch` is already on PATH — no regression |
| 497 | // for hosts that already have it). |
| 498 | _ensureRemoteHelper () { |
| 499 | if (this._remoteHelperDir !== undefined) return this._remoteHelperDir; |
| 500 | this._remoteHelperDir = null; |
| 501 | try { |
| 502 | // git-remote-punch sits as a SIBLING of hyper-sync — in the dev tree |
| 503 | // (hyper-fleeky/{hyper-sync,git-remote-punch}) and when flattened into |
| 504 | // node_modules (hoisted next to us). Also check the nested layout. |
| 505 | const candidates = [ |
| 506 | path.join(__dirname, '..', 'git-remote-punch', 'bin', 'git-remote-punch'), |
| 507 | path.join(__dirname, 'node_modules', 'git-remote-punch', 'bin', 'git-remote-punch') |
| 508 | ]; |
| 509 | let helperBin = null; |
| 510 | for (const c of candidates) { try { if (fs.existsSync(c)) { helperBin = c; break; } } catch {} } |
| 511 | if (!helperBin) return null; |
| 512 | |
| 513 | // git-remote-punch is `#!/usr/bin/env bare`, so the shim runs it under the |
| 514 | // bare running us. Bare.argv[0] is authoritative under bare; execPath is |
| 515 | // the fallback. |
| 516 | const barePath = (typeof Bare !== 'undefined' && Bare.argv && Bare.argv[0]) |
| 517 | ? Bare.argv[0] |
| 518 | : process.execPath; |
| 519 | if (!barePath) return null; |
| 520 | |
| 521 | // Shim lives under the repos dir but NOT as a *.git, so repo scans ignore |
| 522 | // it. Rewritten each boot (cheap) so bare/helper paths stay current. |
| 523 | const dir = path.join(this._reposDir, '.remote-helpers'); |
| 524 | fs.mkdirSync(dir, { recursive: true }); |
| 525 | const shimPath = path.join(dir, 'git-remote-punch'); |
| 526 | fs.writeFileSync(shimPath, '#!/bin/sh\nexec "' + barePath + '" "' + helperBin + '" "$@"\n'); |
| 527 | fs.chmodSync(shimPath, 0o755); |
| 528 | this._remoteHelperDir = dir; |
| 529 | } catch { |
| 530 | this._remoteHelperDir = null; |
| 531 | } |
| 532 | return this._remoteHelperDir; |
| 533 | } |
| 534 | |
| 535 | // Env for a `git` child, with the self-provisioned git-remote-punch helper |
| 536 | // dir prepended to PATH so `git fetch punch://...` resolves the helper. |
| 537 | _gitEnv () { |
| 538 | const dir = this._ensureRemoteHelper(); |
| 539 | const env = Object.assign({}, process.env); |
| 540 | if (dir) env.PATH = dir + path.delimiter + (env.PATH || ''); |
| 541 | return env; |
| 542 | } |
| 543 | |
| 488 | 544 | _gitFetch (repoPath, fetchUrl) { |
| 489 | 545 | return new Promise((resolve, reject) => { |
| 490 | 546 | // '+refs/*:refs/*' — leading '+' means force-update. Lets us |
| … | … | @@ -495,7 +551,7 @@ module.exports = class HyperSync extends ReadyResource { |
| 495 | 551 | // fetch and get reclaimed by the next gc. |
| 496 | 552 | const proc = spawn('git', [ |
| 497 | 553 | '-C', repoPath, 'fetch', fetchUrl, '+refs/*:refs/*', '--no-tags' |
| 498 | | ], { stdio: ['ignore', 'pipe', 'pipe'] }); |
| 554 | ], { stdio: ['ignore', 'pipe', 'pipe'], env: this._gitEnv() }); |
| 499 | 555 | |
| 500 | 556 | const errChunks = []; |
| 501 | 557 | proc.stderr.on('data', (d) => errChunks.push(d)); |