const { spawnSync } = require('node:child_process'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); function runGit(args, { cwd, env, accept = [0] }) { const result = spawnSync('git', args, { cwd, env, encoding: 'utf8' }); if (result.error) throw new Error(`Failed to run git: ${result.error.message}`); if (!accept.includes(result.status)) { throw new Error((result.stderr || result.stdout || `git exited ${result.status}`).trim()); } return result; } function syncRepository({ forgeUrl, upstreamUrl, branch, env }) { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-repo-sync-')); const forgeRef = 'refs/stoke/forge-branch'; const upstreamRef = 'refs/stoke/upstream-branch'; try { runGit(['init', '--bare', directory], { cwd: directory, env }); runGit(['fetch', '--no-tags', forgeUrl, `refs/heads/${branch}:${forgeRef}`], { cwd: directory, env, }); runGit(['fetch', '--no-tags', upstreamUrl, `refs/heads/${branch}:${upstreamRef}`], { cwd: directory, env, }); const oldSha = runGit(['rev-parse', forgeRef], { cwd: directory, env }).stdout.trim(); const newSha = runGit(['rev-parse', upstreamRef], { cwd: directory, env }).stdout.trim(); const ancestry = runGit(['merge-base', '--is-ancestor', oldSha, newSha], { cwd: directory, env, accept: [0, 1], }); if (ancestry.status !== 0) { throw new Error(`Refusing diverged branch ${branch}: forge ${oldSha}, upstream ${newSha}. Diverged trees are out of scope; follow ceremony docs/UPSTREAM-SYNC.md.`); } if (oldSha !== newSha) { runGit(['push', forgeUrl, `${upstreamRef}:refs/heads/${branch}`], { cwd: directory, env, }); } return { branch, oldSha, newSha }; } finally { fs.rmSync(directory, { recursive: true, force: true }); } } module.exports = { syncRepository };