OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / gip-clone-e2e.test.js

98 lines · javascript ref:
// 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')
})