// gip-overlay P3e — two-daemon byte-identical clone over the swarm.
//
// A pushes to its bare repo; the producer projects it (bundle → overlay,
// refs + bundle-chain → the per-repo autobase). B joins the repo over the
// swarm, replicates the autobase, and its RefReconciler materializes B's bare
// repo — fetching objects from A's overlay over the swarm connection. The end
// state: B's bare repo holds the same commit, byte-identical, with no git
// transport between them (only autobase + overlay).
import test from 'brittle'
import process from '#node:process'
import { mkdtempSync, writeFileSync, existsSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import createTestnet from '@hyperswarm/testnet'
import { startPearGit } from '../index.js'
import { execFileSync } from '../lib/_compat-exec.js'
const ZERO = '0000000000000000000000000000000000000000'
let n = 0
const tmpStorage = () => mkdtempSync(join(tmpdir(), 'pgv3-clone-' + (n++) + '-'))
const tmpSock = () => join(mkdtempSync(join(tmpdir(), 'pgv3-clone-sock-' + (n++) + '-')), 'svc.sock')
function git (args, opts = {}) {
return 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 = 30000) {
const t0 = Date.now()
while (Date.now() - t0 < ms) {
try { if (await pred()) return true } catch {}
await new Promise((r) => setTimeout(r, 150))
}
return false
}
test('gip: push on A → byte-identical clone materialized on B over the swarm', async (t) => {
t.timeout(90000)
process.env.PGV3_USE_RAW_IDENTITY = '1'
const testnet = await createTestnet(3, t.teardown)
const A = await startPearGit({
storage: tmpStorage(), uds: tmpSock(), http: { port: 0 },
swarm: { swarm: { bootstrap: testnet.bootstrap } }, deploy: false, sync: false
})
t.teardown(async () => { try { await A.close() } catch {} })
const B = await startPearGit({
storage: tmpStorage(), uds: tmpSock(), http: { port: 0 },
swarm: { swarm: { bootstrap: testnet.bootstrap } }, deploy: false, sync: false
})
t.teardown(async () => { try { await B.close() } catch {} })
const { Client } = await import('hyper-svc')
const ca = await Client.connectUds(A.svc.uds.path)
const cb = await Client.connectUds(B.svc.uds.path)
t.teardown(async () => { try { await ca.close() } catch {}; try { await cb.close() } catch {} })
// ── A: own the repo, push a commit into its bare repo ──────────
await A.issueRegistry.getForRepo('gip-test') // mint owner tracker
const bareA = join(A.svc._reposDir, 'gip-test.git')
git(['init', '--bare', '-q', bareA])
const work = join(A.svc._reposDir, '..', 'work-a')
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'), 'gip-overlay\n')
git(['-C', work, 'add', '.'])
git(['-C', work, 'commit', '-q', '-m', 'initial'])
const oid = git(['-C', work, 'rev-parse', 'HEAD'])
git(['-C', work, 'push', '-q', bareA, 'HEAD:refs/heads/main'])
// fire the producer (as the post-receive hook would)
await ca.call('ref-update-observed', { name: 'gip-test', ref: 'refs/heads/main', oldSha: ZERO, newSha: oid })
// A's autobase now carries the ref + bundle
const aTracker = await A.issueRegistry.getForRepo('gip-test')
const aReady = await waitFor(async () => {
const r = await aTracker.getRef('refs/heads/main')
return r && r.hash === oid && !!(await aTracker.getBundle(oid))
}, 15000)
t.ok(aReady, 'A published the ref + bundle into the per-repo autobase')
// ── B: join the repo over the swarm; reconciler materializes ───
const { invitation } = await ca.call('create-repo-invite', { name: 'gip-test', role: 'contributor' })
const rj = await cb.call('join-repo', { invitation })
t.is(rj.name, 'gip-test', 'B joined the repo over the swarm')
const bareB = join(B.svc._reposDir, 'gip-test.git')
const materialized = await waitFor(() => bareRef(bareB, 'refs/heads/main') === oid, 45000)
t.ok(materialized, 'B materialized refs/heads/main at the pushed oid (no git transport — autobase + overlay)')
t.is(git(['--git-dir', bareB, 'cat-file', '-p', oid + ':README.md']), 'gip-overlay', 'blob byte-identical on B')
})