1import { execSync } from 'child_process' 2import crypto from 'crypto' 8 gitEnv?: Record<string, string>, 11const computeCommitSha = (treeSha: string, authorLine: string, committerLine: string, message: string) => { 12 const body = `tree ${treeSha}\n${authorLine}\n${committerLine}\n\n${message}\n` 13 const header = `commit ${Buffer.byteLength(body)}\0` 14 return crypto.createHash('sha1').update(header + body).digest('hex') 17export const commitMineShaPrefix = ({cwd, msgBase, tgtShaPrefix, gitEnv}: MineOpts): string => { 18 const env = gitEnv ? {...process.env, ...gitEnv} : undefined 19 const run = (cmd: string) => execSync(cmd, {cwd, stdio: 'pipe', encoding: 'utf8', env}) 21 const treeSha = run('git write-tree').trim() 24 const sha = run(`git commit-tree ${treeSha} -m "${msgBase} #0"`).trim() 25 run(`git update-ref HEAD ${sha}`) 29 // Resolve author/committer identity (env overrides > git config fallback) 30 const cfg = (k: string) => { try { return run(`git config ${k}`).trim() } catch { return '' } } 31 const authorName = gitEnv?.GIT_AUTHOR_NAME || gitEnv?.GIT_COMMITTER_NAME || cfg('user.name') 32 const authorEmail = gitEnv?.GIT_AUTHOR_EMAIL || gitEnv?.GIT_COMMITTER_EMAIL || cfg('user.email') 33 const committerName = gitEnv?.GIT_COMMITTER_NAME || authorName 34 const committerEmail = gitEnv?.GIT_COMMITTER_EMAIL || authorEmail 36 // Fix timestamp so JS-computed SHA matches the final git commit-tree call 37 const ts = Math.floor(Date.now() / 1000) 38 const tzOff = -new Date().getTimezoneOffset() 39 const tz = `${tzOff >= 0 ? '+' : '-'}${String(Math.floor(Math.abs(tzOff) / 60)).padStart(2, '0')}${String(Math.abs(tzOff) % 60).padStart(2, '0')}` 40 const gitDate = `${ts} ${tz}` 41 const authorLine = `author ${authorName} <${authorEmail}> ${ts} ${tz}` 42 const committerLine = `committer ${committerName} <${committerEmail}> ${ts} ${tz}` 44 // Mine: pure-JS SHA1 loop (~µs/iter), then single git commit-tree for the winner 45 let nonce = 0, sha = '' 46 for (; nonce < 1_000_000; nonce++) { 47 const candidate = computeCommitSha(treeSha, authorLine, committerLine, `${msgBase} #${nonce}`) 48 if (candidate.startsWith(tgtShaPrefix)) { sha = candidate; break } 50 if (!sha) throw new Error(`mining sha prefix "${tgtShaPrefix}" failed after 1M attempts`) 52 const commitEnv = {...process.env, ...gitEnv, GIT_AUTHOR_DATE: gitDate, GIT_COMMITTER_DATE: gitDate} as Record<string, string> 53 const created = execSync(`git commit-tree ${treeSha} -m "${msgBase} #${nonce}"`, {cwd, stdio: 'pipe', encoding: 'utf8', env: commitEnv as any}).trim() 54 if (created !== sha) throw new Error(`mining sha mismatch: JS=${sha.slice(0,12)} git=${created.slice(0,12)}`) 55 run(`git update-ref HEAD ${sha}`) 56 console.log(` mined sha prefix "${tgtShaPrefix}" after ${nonce + 1} attempt(s): ${sha.slice(0, 12)}`)