// #16 END-TO-END CONVERGENCE: a repo created on the MIRROR materializes on the
// SOURCE via the reverse RepoPuller.
//
// This exercises the real #16 machinery end to end:
// - a REAL RepoEventLog on the mirror, appended when the mirror "creates" a
// repo, REPLICATED to the source over a REAL Hyperswarm (testnet) via
// corestore.replicate — exactly as two daemons do;
// - the REAL reverse RepoPuller (lib/services/repo-mirror.js) the source's
// watcher boots on a peer's `__server__`/`__repoeventlog__` announcement,
// streaming that log and pulling each repo;
// - REAL git materialization: the pull does init-if-missing + `git fetch`
// (the same shape as hyper-sync.pull) so the source ends up with a
// byte-identical bare repo.
//
// The ONLY substitution is the fetch TRANSPORT: hyper-sync fetches over
// `punch://` (needs a git-remote-punch helper); here the two "daemons" share a
// host so we fetch from the mirror's on-disk bare repo via a local path. The
// transport is orthogonal to #16 — the fix is about DISCOVERY (source learns
// of + pulls the mirror's repo), which is fully exercised.
//
// Companion: bidirectional-repo-discovery.test.js proves a real startPearGit
// daemon BOOTS the reverse puller from the announcement. Together they cover
// discovery announce → boot → stream → pull → materialize.
import test from 'brittle'
import { mkdtempSync, existsSync, writeFileSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import b4a from 'b4a'
import Corestore from 'corestore'
import createTestnet from '@hyperswarm/testnet'
import Hyperswarm from 'hyperswarm'
import { execFileSync } from '../lib/_compat-exec.js'
import { RepoEventLog } from '../lib/services/repo-event-log.js'
import { RepoPuller } from '../lib/services/repo-mirror.js'
let n = 0
const tmp = (tag) => mkdtempSync(join(tmpdir(), 'pgv3-e2e-' + tag + '-' + (n++) + '-'))
const git = (args, opts = {}) => execFileSync('git', args, { encoding: 'utf8', ...opts }).toString().trim()
function bareRef (bare, ref) {
try { return git(['--git-dir', bare, 'rev-parse', '--verify', '--quiet', ref]) || null } catch { return null }
}
async function waitFor (pred, ms = 20000) {
const t0 = Date.now()
while (Date.now() - t0 < ms) {
try { if (await pred()) return true } catch {}
await new Promise((r) => setTimeout(r, 100))
}
return false
}
// Real sync adapter: same contract the RepoPuller uses on hyper-sync
// (`remotes` Map, addRemote, pull) and the same init-if-missing + git fetch
// behavior — but fetches from the remote's on-disk bare repo via a local path
// instead of punch:// (transport is orthogonal to #16; see header).
class LocalFetchSync {
constructor (sourceReposDir) {
this._reposDir = sourceReposDir
this.remotes = new Map() // name -> { bareDirFor(name) }
this.served = new Map() // name -> bare path (source's own served repos)
this.pulls = []
}
addRemote (name, remoteReposDir) { this.remotes.set(name, remoteReposDir) }
async pull (remoteName, repoName) {
this.pulls.push({ remoteName, repoName })
const remoteReposDir = this.remotes.get(remoteName)
if (!remoteReposDir) throw new Error('unknown remote ' + remoteName)
const remoteBare = join(remoteReposDir, repoName + '.git')
const localBare = join(this._reposDir, repoName + '.git')
if (!existsSync(localBare)) git(['init', '--bare', '-q', localBare]) // init-if-missing
git(['-C', localBare, 'fetch', remoteBare, '+refs/*:refs/*', '--no-tags']) // fetch-if-present
this.served.set(repoName, localBare)
}
}
test('#16 e2e: a repo created on the mirror converges onto the source via the reverse RepoPuller', async (t) => {
t.timeout(90000)
const testnet = await createTestnet(3, t.teardown)
const topic = b4a.alloc(32, 'pgv3-e2e-16')
// ── MIRROR: real bare repo + a commit, and a real RepoEventLog ──
const mirrorReposDir = tmp('mirror-repos')
const mirrorBare = join(mirrorReposDir, 'mirror-made.git')
git(['init', '--bare', '-q', mirrorBare])
const work = tmp('work')
git(['init', '-q', work])
git(['-C', work, 'config', 'user.email', 't@t'])
git(['-C', work, 'config', 'user.name', 'T'])
git(['-C', work, 'config', 'commit.gpgsign', 'false'])
writeFileSync(join(work, 'README.md'), 'made-on-the-mirror\n')
git(['-C', work, 'add', '.'])
git(['-C', work, 'commit', '-q', '-m', 'created on mirror'])
const oid = git(['-C', work, 'rev-parse', 'HEAD'])
git(['-C', work, 'push', '-q', mirrorBare, 'HEAD:refs/heads/main'])
t.is(bareRef(mirrorBare, 'refs/heads/main'), oid, 'precondition: mirror serves mirror-made@main')
const mirrorStore = new Corestore(tmp('mirror-store'))
await mirrorStore.ready()
const mirrorLog = new RepoEventLog({ corestore: mirrorStore })
await mirrorLog.ready()
const mirrorSwarm = new Hyperswarm({ bootstrap: testnet.bootstrap })
mirrorSwarm.on('connection', (conn) => mirrorStore.replicate(conn))
mirrorSwarm.join(topic, { server: true, client: false })
await mirrorSwarm.flush()
// The mirror's own serve-hook would append this 'created' entry.
await mirrorLog.append({ op: 0, name: 'mirror-made', head: oid })
// ── SOURCE: opens the mirror's RepoEventLog BY KEY (real replication) ──
const sourceReposDir = tmp('source-repos')
const sourceStore = new Corestore(tmp('source-store'))
await sourceStore.ready()
const sourceView = new RepoEventLog({ corestore: sourceStore, key: mirrorLog.keyHex })
await sourceView.ready()
const sourceSwarm = new Hyperswarm({ bootstrap: testnet.bootstrap })
sourceSwarm.on('connection', (conn) => sourceStore.replicate(conn))
sourceSwarm.join(topic, { server: false, client: true })
await sourceSwarm.flush()
t.teardown(async () => {
try { await mirrorSwarm.destroy() } catch {}
try { await sourceSwarm.destroy() } catch {}
try { await mirrorLog.close() } catch {}
try { await sourceView.close() } catch {}
try { await mirrorStore.close() } catch {}
try { await sourceStore.close() } catch {}
})
// Precondition: the source does NOT have the mirror-made repo.
t.absent(existsSync(join(sourceReposDir, 'mirror-made.git')), 'precondition: source lacks mirror-made before sync')
// ── The #16 reverse RepoPuller the source's watcher boots for this peer ──
const sync = new LocalFetchSync(sourceReposDir)
sync.addRemote('peer-mirror', mirrorReposDir) // stands in for the mirror's punch:// serverKey
const puller = new RepoPuller({
sync,
log: sourceView,
primaryServerKey: 'f'.repeat(64),
remoteName: 'peer-mirror',
cursorPath: join(tmp('cursor'), 'c.json')
})
t.teardown(async () => { try { await puller.close() } catch {} })
await puller.start()
// ── ASSERT CONVERGENCE: the mirror-created repo materializes on the source ──
const converged = await waitFor(() => bareRef(join(sourceReposDir, 'mirror-made.git'), 'refs/heads/main') === oid, 25000)
t.ok(converged, 'source materialized mirror-made@main via the reverse puller (110→112 convergence)')
t.is(bareRef(join(sourceReposDir, 'mirror-made.git'), 'refs/heads/main'), oid, 'source ref == mirror ref (same oid)')
t.is(
git(['--git-dir', join(sourceReposDir, 'mirror-made.git'), 'cat-file', '-p', oid + ':README.md']),
'made-on-the-mirror',
'blob byte-identical on the source (real content converged, not just a ref)'
)
t.ok(sync.pulls.some(p => p.repoName === 'mirror-made' && p.remoteName === 'peer-mirror'),
'reverse puller pulled mirror-made from the mirror peer')
})