import { randomUUID } from 'node:crypto'; import { setTimeout as sleep } from 'node:timers/promises'; import { git } from './lib.mjs'; export const lockRef = 'refs/heads/automation/release-lock'; export async function acquireLock({ runGit = git, wait = sleep, attempts = 480, ref = lockRef } = {}) { const lockSha = runGit(['-c', 'user.name=FEDEO Release', '-c', 'user.email=release@fedeo.de', 'commit-tree', 'HEAD^{tree}', '-p', 'HEAD', '-m', `chore(release): lock ${randomUUID()}`]); for (let attempt = 0; attempt < attempts; attempt++) { const existing = runGit(['ls-remote', 'origin', ref]); if (!existing) { try { // An empty lease means the ref MUST NOT exist, even if another runner just created it. runGit(['push', `--force-with-lease=${ref}:`, 'origin', `${lockSha}:${ref}`]); return () => runGit(['push', `--force-with-lease=${ref}:${lockSha}`, 'origin', `:${ref}`]); } catch (error) { if (/403|forbidden|permission denied|authentication failed/i.test(String(error.stderr || error.message))) { throw new Error('Die main-Release-Sperre benötigt Git-Schreibrechte. RELEASE_TOKEN (Secret oder Variable) oder den bisherigen vars.CI_TOKEN mit write:repository und Zugriff auf automation/release-lock konfigurieren.'); } if (!runGit(['ls-remote', 'origin', ref])) throw error; } } if (attempt % 4 === 0) console.log('Ein anderer Release läuft; warte auf die Git-Sperre.'); await wait(15_000); } throw new Error(`Release-Sperre ${ref} bleibt belegt. Siehe docs/releases.md zur Wiederaufnahme.`); }