OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / gip-publish-roundtrip.test.js

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