Files
hermes-agent/apps/desktop/electron/gitlock.test.ts
T
Teknium ed4f91c4eb fix(update): widen stale-lock self-heal to banner and desktop; align with compare-API status
Follow-up on the cherry-picked gitlock work (#80501 by @RGerrish, covering
the #75133 / #75168 wedge first reported and fixed by @RelaxJonh):

- Drop the PR's ancestor-check halves in banner.py, update-count.ts and
  main.ts: superseded by the compare-API status recovery that landed in
  #86257/#86331 (ahead_by == 0 already reports local-ahead as up to date).
  The salvaged update_cmd.py check path keeps main's compare-API structure
  instead of the PR's tip-SHA-plus-ancestry print.
- Keep and wire clear_stale_git_locks() at the remaining wedge sites the
  original PR targeted: hermes update apply, hermes update --check, and the
  passive banner check.
- Add the desktop counterpart (electron/gitlock.ts) so checkUpdates() heals
  the same wedge instead of reporting fetch-failed forever; mirrored
  age + git-process guards; vitest coverage.

E2E verified: real --depth 1 clone with an aged .git/shallow.lock reproduces
"Unable to create '.git/shallow.lock': File exists"; clear_stale_git_locks
removes it and the fetch succeeds; a fresh lock (in-flight fetch) is
preserved.
2026-08-15 04:33:47 -07:00

70 lines
2.4 KiB
TypeScript

import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { test } from 'vitest'
import { clearStaleGitLocks, LOCK_NAMES, STALE_LOCK_MIN_AGE_MS } from './gitlock'
function makeRepo(): string {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitlock-test-'))
fs.mkdirSync(path.join(root, '.git'))
return root
}
function writeLock(root: string, name: string, ageMs: number): string {
const p = path.join(root, '.git', name)
fs.writeFileSync(p, '')
const t = new Date(Date.now() - ageMs)
fs.utimesSync(p, t, t)
return p
}
const noGit = async () => false
const gitRunning = async () => true
test('stale shallow.lock older than min age is removed', async () => {
const root = makeRepo()
const lock = writeLock(root, 'shallow.lock', STALE_LOCK_MIN_AGE_MS + 60_000)
const removed = await clearStaleGitLocks(root, { isGitRunning: noGit })
assert.deepEqual(removed, [lock])
assert.equal(fs.existsSync(lock), false)
})
test('fresh lock is presumed live and never removed', async () => {
const root = makeRepo()
const lock = writeLock(root, 'shallow.lock', 1_000)
const removed = await clearStaleGitLocks(root, { isGitRunning: noGit })
assert.deepEqual(removed, [])
assert.equal(fs.existsSync(lock), true)
})
test('running git process protects even ancient locks', async () => {
const root = makeRepo()
const lock = writeLock(root, 'shallow.lock', STALE_LOCK_MIN_AGE_MS * 10)
const removed = await clearStaleGitLocks(root, { isGitRunning: gitRunning })
assert.deepEqual(removed, [])
assert.equal(fs.existsSync(lock), true)
})
test('all known lock names are cleared when stale', async () => {
const root = makeRepo()
const locks = LOCK_NAMES.map(name => writeLock(root, name, STALE_LOCK_MIN_AGE_MS + 60_000))
const removed = await clearStaleGitLocks(root, { isGitRunning: noGit })
assert.deepEqual(removed.sort(), locks.sort())
})
test('unknown lock-like files are left alone', async () => {
const root = makeRepo()
const stray = writeLock(root, 'config.lock', STALE_LOCK_MIN_AGE_MS * 10)
await clearStaleGitLocks(root, { isGitRunning: noGit })
assert.equal(fs.existsSync(stray), true)
})
test('missing .git dir is a silent no-op', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitlock-nogit-'))
const removed = await clearStaleGitLocks(root, { isGitRunning: noGit })
assert.deepEqual(removed, [])
})