OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / lib / gip-publish.js

59 lines · javascript ref:
/**
 * 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 }
}