/**
* gip-publish — gip-overlay P3 producer.
*
* When a push advances a ref on a writer node, project it into the P2P layer:
* 1. bundle the delta (oldOid..newOid) with `git bundle`
* 2. register the bundle file in the overlay (servable by content hash)
* 3. record the bundle in the per-repo autobase (add-bundle, P3b)
* 4. advance the ref in the autobase (update-ref, FF-enforced — P1)
*
* Order matters: the bundle is published and recorded BEFORE the ref moves,
* so a peer that observes the ref advance (via autobase replication) can
* already resolve + fetch the objects. The bundle file is left on disk in
* `spoolDir` — it is the overlay's canonical byte source.
*/
import fs from '#node:fs'
import path from '#node:path'
import { createDeltaBundle } from './git-bundle.js'
import { ZERO_OID } from 'hyper-issues'
/**
* @param {object} o
* o.repoPath bare repo (--git-dir) the push landed in
* o.repoName repo name (overlay path namespacing)
* o.ref ref advanced (e.g. refs/heads/main)
* o.oldOid previous oid (ZERO/null ⇒ ref creation)
* o.newOid new oid
* o.tracker per-repo HyperIssues (must be writable)
* o.overlay HyperOverlayV2-shaped { registerFile(path, disk, meta) -> { contentHash, size } }
* o.spoolDir persistent dir for bundle files (overlay's byte source)
* o.userId signing user id recorded on the ops
* @returns {Promise<{ contentHash, bundlePath, size } | null>} null for a no-op move
*/
export async function publishRefUpdate (o = {}) {
const { repoPath, repoName, ref, newOid, tracker, overlay, spoolDir, userId } = o
const oldOid = o.oldOid || ZERO_OID
if (!repoPath || !ref || !newOid || !tracker || !overlay || !spoolDir) {
throw new Error('publishRefUpdate requires { repoPath, ref, newOid, tracker, overlay, spoolDir }')
}
const repoDir = path.join(spoolDir, repoName || path.basename(repoPath).replace(/\.git$/, ''))
fs.mkdirSync(repoDir, { recursive: true })
const bundlePath = path.join(repoDir, newOid + '.bundle')
const bundle = await createDeltaBundle({ repoPath, ref, oldOid, newOid, outPath: bundlePath })
if (!bundle) return null // empty delta — nothing to publish
const overlayPath = '/git/' + (repoName || path.basename(repoPath).replace(/\.git$/, '')) + '/' + newOid + '.bundle'
const { contentHash, size } = await overlay.registerFile(overlayPath, bundlePath, {})
// Record the bundle BEFORE moving the ref so a peer that sees the ref
// advance can already resolve the chain + fetch the objects.
await tracker.addBundle({ oid: newOid, baseOid: oldOid, contentHash, size, ref }, userId)
await tracker.updateRef(ref, newOid, userId, oldOid)
return { contentHash, bundlePath, size }
}