// A mirror's repo listing went stale the moment a pull landed.
//
// RepoPuller._process fetches into an EXISTING bare repo. Nothing rescans
// afterwards: the repos-dir watcher (services/sync.js) is a non-recursive
// watch(reposDir), so writes INSIDE <repo>.git never wake it, and the
// puller's only hook is onMount → scheduleMirrorReannounce. So
// sync.status() — which feeds list-repos and the /repos ordering — kept
// serving whatever the last full scan saw.
//
// Measured on the live fleet 2026-09-05: stortron (up 3d) reported
// blender-bridge lastUpdated 2026-06-12 while its OWN disk said
// 2026-09-04 15:57 — the refs were 2s behind tabertron's. Four repos were
// buried on one backend and top-of-page on the other for this reason alone.
//
// Fix: refresh THAT repo's record after a successful pull. Per-repo, not a
// full sync.scan() — scanRepos walks every repo synchronously (364ms across
// 147 live repos) and would stall the event loop once per pulled repo.
import test from 'brittle'
import fs from '#node:fs'
import os from '#node:os'
import { join } from '#node:path'
import { RepoPuller } from '../lib/services/repo-mirror.js'
import { attachRepoRefresh } from '../lib/services/sync.js'
function tmpdir (t) {
const dir = fs.mkdtempSync(join(os.tmpdir(), 'pgv3-refresh-'))
t.teardown(() => { try { fs.rmSync(dir, { recursive: true, force: true }) } catch {} })
return dir
}
function bareRepo (dir, name, sha) {
const p = join(dir, name + '.git')
fs.mkdirSync(join(p, 'refs', 'heads'), { recursive: true })
fs.mkdirSync(join(p, 'objects', 'pack'), { recursive: true })
fs.writeFileSync(join(p, 'HEAD'), 'ref: refs/heads/main\n')
fs.writeFileSync(join(p, 'refs', 'heads', 'main'), sha + '\n')
return p
}
test('RepoPuller refreshes the pulled repo, and only on success', async (t) => {
const ok = []
const good = new RepoPuller({
sync: {
pull: async () => {},
remotes: new Map(),
addRemote () {},
refreshRepo: (name) => ok.push(name)
},
issueRegistry: {},
log: { close: async () => {} },
primaryServerKey: 'c'.repeat(64)
})
await good._process({ name: 'demo', op: 0 }, 0)
t.alike(ok, ['demo'], 'refreshRepo called with the repo name on success')
const bad = []
const fails = new RepoPuller({
sync: {
pull: async () => { throw new Error('boom') },
remotes: new Map(),
addRemote () {},
refreshRepo: (name) => bad.push(name)
},
issueRegistry: {},
log: { close: async () => {} },
primaryServerKey: 'd'.repeat(64)
})
await fails._process({ name: 'nope', op: 0 }, 0)
t.alike(bad, [], 'refreshRepo NOT called when the pull fails')
})
test('RepoPuller survives a sync without refreshRepo (older hyper-sync)', async (t) => {
const puller = new RepoPuller({
sync: { pull: async () => {}, remotes: new Map(), addRemote () {} },
issueRegistry: {},
log: { close: async () => {} },
primaryServerKey: 'e'.repeat(64)
})
await puller._process({ name: 'demo', op: 0 }, 0)
t.pass('no throw when the sync server has no refreshRepo')
})
test('attachRepoRefresh re-stats ONE repo without walking the others', async (t) => {
const dir = tmpdir(t)
bareRepo(dir, 'alpha', 'a'.repeat(40))
bareRepo(dir, 'beta', 'b'.repeat(40))
// Model a daemon whose in-memory map is stale for alpha: an old lastUpdated
// and no branches, exactly what a post-boot pull leaves behind.
const stale = { name: 'alpha', path: join(dir, 'alpha.git'), branches: [], branchCount: 0, head: null, packSize: 0, lastUpdated: 1, footprint: 0 }
const betaEntry = { name: 'beta', path: join(dir, 'beta.git'), branches: [], branchCount: 0, head: null, packSize: 0, lastUpdated: 2, footprint: 0 }
const sync = { _reposDir: dir, repos: new Map([['alpha', stale], ['beta', betaEntry]]) }
attachRepoRefresh(sync, dir)
const updated = sync.refreshRepo('alpha')
t.ok(updated, 'returns the refreshed record')
t.is(sync.repos.get('alpha').branchCount, 1, 'alpha picked up its branch')
t.is(sync.repos.get('alpha').head, 'a'.repeat(40), 'alpha picked up its head')
t.ok(sync.repos.get('alpha').lastUpdated > 1, 'alpha lastUpdated moved to the real ref mtime')
t.is(sync.repos.get('beta'), betaEntry, 'beta was left untouched — not a full rescan')
})
test('attachRepoRefresh is a no-op for an unknown or vanished repo', async (t) => {
const dir = tmpdir(t)
const sync = { _reposDir: dir, repos: new Map() }
attachRepoRefresh(sync, dir)
t.is(sync.refreshRepo('ghost'), null, 'unknown repo returns null')
t.is(sync.refreshRepo(''), null, 'empty name returns null')
t.is(sync.repos.size, 0, 'nothing was added to the map')
})