// gip-overlay P3d — producer ↔ overlay ↔ fetcher ↔ reconciler, one process.
//
// The full loop with the REAL HyperOverlayV2 (register + local fetch round-
// trips without peers): two pushes on an origin bare repo are published
// (bundle → overlay → add-bundle → update-ref), then a SECOND, empty bare repo
// is brought to the same state purely from the autobase view + overlay via
// RefReconciler — byte-identical. The two-daemon-over-swarm version is P3e.
import test from 'brittle'
import process from '#node:process'
import Corestore from 'corestore'
import { mkdtempSync, writeFileSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import { HyperIssues } from 'hyper-issues'
// hyper-overlay exports the class as `HyperOverlay`; alias it the same way
// lib/pear-git.js does so the V2-era call sites below keep reading naturally.
import { HyperOverlay as HyperOverlayV2 } from 'hyper-overlay'
import { execFileSync } from '../lib/_compat-exec.js'
import { publishRefUpdate } from '../lib/gip-publish.js'
import { RefReconciler } from '../lib/services/ref-reconciler.js'
import { makeOverlayEnsureObjects } from '../lib/git-overlay-fetch.js'
function git (args, opts = {}) {
return execFileSync('git', args, { encoding: 'utf8', ...opts }).toString().trim()
}
function has (bare, oid) {
try { git(['--git-dir', bare, 'cat-file', '-e', oid + '^{object}']); return true } catch { return false }
}
test('push on origin → published → materialized byte-identical on a fresh repo', async (t) => {
const dir = mkdtempSync(join(tmpdir(), 'pgv3-gip-'))
// origin bare repo with two commits on main (o1 -> o2)
const origin = join(dir, 'origin.git')
const work = join(dir, 'work')
git(['init', '--bare', '-q', origin])
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, 'a.txt'), 'one\n')
git(['-C', work, 'add', '.'])
git(['-C', work, 'commit', '-q', '-m', 'c1'])
const o1 = git(['-C', work, 'rev-parse', 'HEAD'])
writeFileSync(join(work, 'a.txt'), 'two\n')
git(['-C', work, 'commit', '-q', '-am', 'c2'])
const o2 = git(['-C', work, 'rev-parse', 'HEAD'])
git(['-C', work, 'push', '-q', origin, 'HEAD:refs/heads/main'])
// real overlay + real tracker
const store = new Corestore(join(dir, 'store'))
const overlay = new HyperOverlayV2(store, { namespace: 'overlay-v2', destDir: join(dir, 'fetch') })
await overlay.ready()
const tracker = new HyperIssues(store, { namespace: 'issues:repo:gip-test' })
await tracker.ready()
t.teardown(async () => {
try { await tracker.close() } catch {}
try { await overlay.close() } catch {}
try { await store.close() } catch {}
})
const spoolDir = join(dir, 'gip-bundles')
// publish the two ref advances (as the post-receive producer would)
const r1 = await publishRefUpdate({ repoPath: origin, repoName: 'gip-test', ref: 'refs/heads/main', oldOid: null, newOid: o1, tracker, overlay, spoolDir, userId: 'u' })
t.ok(r1 && r1.contentHash, 'creation bundle published + recorded')
const r2 = await publishRefUpdate({ repoPath: origin, repoName: 'gip-test', ref: 'refs/heads/main', oldOid: o1, newOid: o2, tracker, overlay, spoolDir, userId: 'u' })
t.ok(r2 && r2.contentHash, 'delta bundle published + recorded')
// the autobase view is authoritative
t.is((await tracker.getRef('refs/heads/main')).hash, o2, 'view ref at o2')
t.is((await tracker.getBundle(o2)).baseOid, o1, 'bundle chain links o2 -> o1')
// fresh, empty bare repo follows the view, fetching objects from the overlay
const target = join(dir, 'target.git')
git(['init', '--bare', '-q', target])
const ensureObjects = makeOverlayEnsureObjects({ tracker, overlay, repoPath: target })
const rec = new RefReconciler({ tracker, repoPath: target, ensureObjects })
await rec.reconcile()
t.is(git(['--git-dir', target, 'rev-parse', 'refs/heads/main']), o2, 'target ref materialized at o2')
t.ok(has(target, o1) && has(target, o2), 'both objects present')
t.is(git(['--git-dir', target, 'cat-file', '-p', o2 + ':a.txt']), 'two', 'blob content byte-identical')
})