// RefReconciler reports the repo's newest REPLICATED ref timestamp.
//
// /repos orders on this instead of the local mtime — see
// services/repo-ref-activity.js for the full why. What matters here is that
// the reporting is FREE: _reconcileOnce already calls listRefs(), which is the
// `base.update()` plus ref range-scan the whole sweep-skip machinery exists to
// avoid repeating. Taking max(timestamp) off the array it already has adds no
// autobase work at all.
//
// It must also fire on a NO-OP pass. The value is a property of the view, not
// of whether this daemon's disk changed — on a mirror that is already in sync
// (the common case, and exactly the backend the LB fronts) every pass is a
// no-op, and gating on `changed` like onChange does would leave the ordering
// key permanently unset there.
import test from 'brittle'
import { mkdtempSync, writeFileSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import { RefReconciler } from '../lib/services/ref-reconciler.js'
import { execFileSync } from '../lib/_compat-exec.js'
function git (args, opts = {}) {
return execFileSync('git', args, { encoding: 'utf8', ...opts }).toString().trim()
}
function fakeTracker (refs) {
return {
base: { on () {}, off () {} },
async listRefs () { return refs.slice() }
}
}
function makeRepo () {
const dir = mkdtempSync(join(tmpdir(), 'pgv3-refact-rec-'))
const bare = join(dir, 'repo.git')
const work = join(dir, 'work')
git(['init', '--bare', '-q', bare])
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'])
git(['-C', work, 'push', '-q', bare, 'HEAD:refs/heads/main'])
return { bare, o1 }
}
test('reports the newest ref timestamp across the view', async (t) => {
const { bare, o1 } = makeRepo()
const seen = []
const tracker = fakeTracker([
{ ref: 'refs/heads/main', hash: o1, timestamp: 1000 },
{ ref: 'refs/heads/old', hash: o1, timestamp: 500 },
{ ref: 'refs/heads/new', hash: o1, timestamp: 7000 }
])
const rec = new RefReconciler({
tracker,
repoPath: bare,
onRefActivity: (ms) => seen.push(ms)
})
await rec.reconcile()
t.alike(seen, [7000], 'the max, not the first or the last')
})
test('fires on a no-op pass — a synced mirror must still report', async (t) => {
const { bare, o1 } = makeRepo()
const seen = []
// The bare repo is ALREADY at o1, so this pass changes nothing on disk.
const tracker = fakeTracker([{ ref: 'refs/heads/main', hash: o1, timestamp: 4242 }])
const rec = new RefReconciler({ tracker, repoPath: bare, onRefActivity: (ms) => seen.push(ms) })
await rec.reconcile()
t.is(git(['--git-dir', bare, 'rev-parse', 'refs/heads/main']), o1, 'nothing moved on disk')
t.alike(seen, [4242], 'reported anyway')
})
test('stays silent when the view carries no usable timestamp', async (t) => {
const { bare, o1 } = makeRepo()
const seen = []
// A ref-view written by a build that predates the timestamp field, or an
// empty view. Reporting 0 would rank the repo below everything.
const tracker = fakeTracker([{ ref: 'refs/heads/main', hash: o1 }])
const rec = new RefReconciler({ tracker, repoPath: bare, onRefActivity: (ms) => seen.push(ms) })
await rec.reconcile()
t.alike(seen, [], 'no timestamp, no report')
const empty = new RefReconciler({
tracker: fakeTracker([]),
repoPath: bare,
onRefActivity: (ms) => seen.push(ms)
})
await empty.reconcile()
t.alike(seen, [], 'empty view, no report')
})
test('a failing onRefActivity never breaks reconciliation', async (t) => {
const { bare, o1 } = makeRepo()
git(['--git-dir', bare, 'update-ref', '-d', 'refs/heads/main'])
const rec = new RefReconciler({
tracker: fakeTracker([{ ref: 'refs/heads/main', hash: o1, timestamp: 1 }]),
repoPath: bare,
onRefActivity: () => { throw new Error('index exploded') }
})
await rec.reconcile()
t.is(git(['--git-dir', bare, 'rev-parse', 'refs/heads/main']), o1,
'the ref was still materialized despite the hook throwing')
})