2026-08-31 16:51:04 +00:00
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 } ) ;
2026-08-31 16:54:34 +00:00
if ( ! branch ) {
const symbolicHead = runGit ( [ 'ls-remote' , '--symref' , forgeUrl , 'HEAD' ] , {
cwd : directory ,
env ,
} ) . stdout ;
const match = symbolicHead . match ( /^ref:\s+refs\/heads\/(.+)\s+HEAD$/m ) ;
if ( ! match ) throw new Error ( 'Could not resolve the forge repository default branch' ) ;
branch = match [ 1 ] ;
}
2026-08-31 16:51:04 +00:00
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 ,
} ) ;
}
2026-08-31 16:54:34 +00:00
return { branch , oldSha , newSha , changed : oldSha !== newSha } ;
2026-08-31 16:51:04 +00:00
} finally {
fs . rmSync ( directory , { recursive : true , force : true } ) ;
}
}
module . exports = { syncRepository } ;