import path from "node:path"; import { $ as _$ } from "bun"; import { MAX_BRANCH_CACHE, MAX_TAG_CACHE, paths, REF_CACHE_TTL_MS, } from "../constants.ts"; const gitEnv = { ...process.env, LC_ALL: "C", LANG: "C", GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_SYSTEM: "/dev/null", GIT_CONFIG_COUNT: "0", GIT_ASKPASS: "echo", GIT_TERMINAL_PROMPT: "0", }; const $ = _$.env(gitEnv); // Per-repo mutex: prevents concurrent git write operations on the same repo // (e.g. two patches being merged simultaneously, which would corrupt the index). const repoWriteLocks = new Map>(); // Short-lived caches for ref lists — these change only on push/branch ops. const branchCache = new Map(); const tagCache = new Map(); export function invalidateRefCache(name: string): void { branchCache.delete(name); tagCache.delete(name); } async function withRepoLock(name: string, fn: () => Promise): Promise { const prev = repoWriteLocks.get(name) ?? Promise.resolve(); let unlock!: () => void; repoWriteLocks.set( name, prev.then( () => new Promise((res) => { unlock = res; }), ), ); await prev; try { return await fn(); } finally { unlock(); } } export function repoPath(name: string): string { return path.join(paths.REPOS_DIR, `${name}.git`); } export async function validateCommit( repoName: string, hash: string, ): Promise { const p = repoPath(repoName); try { const out = await $`git -C ${p} cat-file -t ${hash}`.text(); return out.trim() === "commit"; } catch { return false; } } export async function archiveRepo( repoName: string, ref: string, slug: string, outDir: string, signal?: AbortSignal, ): Promise { const p = repoPath(repoName); const base = `${slug}-${ref}`; const zip = Bun.spawn( [ "git", "-C", p, "archive", "--format=zip", `--output=${path.join(outDir, `${base}.zip`)}`, ref, ], { signal, env: gitEnv }, ); if ((await zip.exited) !== 0) throw new Error("git archive (zip) failed"); const tgz = Bun.spawn( [ "git", "-C", p, "archive", "--format=tar.gz", `--output=${path.join(outDir, `${base}.tar.gz`)}`, ref, ], { signal, env: gitEnv }, ); if ((await tgz.exited) !== 0) throw new Error("git archive (tar.gz) failed"); try { const tar = Bun.spawn( ["git", "-C", p, "archive", "--format=tar", ref], { signal, env: gitEnv, stdout: "pipe" }, ); const zst = Bun.spawn( ["zstd", "-o", path.join(outDir, `${base}.tar.zst`)], { signal, env: gitEnv, stdin: tar.stdout }, ); await Promise.all([tar.exited, zst.exited]); } catch { // zstd not available — skip silently } } export interface CommitEntry { hash: string; subject: string; author: string; date: string; sigStatus: "good" | "bad" | "none"; } export interface CommitMeta { hash: string; subject: string; body: string; author: string; email: string; date: string; committer: string; committerEmail: string; committerDate: string; parents: string[]; sigStatus: "good" | "bad" | "none"; } export interface TreeEntry { mode: string; type: "blob" | "tree"; hash: string; size: string; name: string; } function parseSigStatus(code: string): "good" | "bad" | "none" { if (code === "G" || code === "X" || code === "Y" || code === "R") return "good"; if (code === "B" || code === "U" || code === "E") return "bad"; return "none"; } function parseLog(out: string): CommitEntry[] { return out .split("\n") .filter(Boolean) .map((line) => { const parts = line.split("\x1f"); return { hash: parts[0] ?? "", subject: parts[1] ?? "", author: parts[2] ?? "", date: parts[3] ?? "", sigStatus: parseSigStatus(parts[4] ?? ""), }; }); } function parseLsTree(out: string): TreeEntry[] { return out .split("\n") .filter(Boolean) .map((line) => { // format: SP SP SP TAB const tabIdx = line.indexOf("\t"); const name = line.slice(tabIdx + 1); const meta = line.slice(0, tabIdx).trim().split(/\s+/); return { mode: meta[0] ?? "", type: (meta[1] ?? "blob") as "blob" | "tree", hash: meta[2] ?? "", size: meta[3] ?? "-", name, }; }); } export function extractPatchSubject(patch: string): string { for (const line of patch.split("\n").slice(0, 30)) { if (line.startsWith("Subject: ")) { // Strip "[PATCH ...] " prefix added by git format-patch return line.slice(9).replace(/^\[PATCH[^\]]*\]\s*/, ""); } } return ""; } export interface BranchInfo { name: string; shortHash: string; subject: string; authorName: string; date: string; } export interface TagInfo { name: string; shortHash: string; subject: string; taggerName: string; date: string; isAnnotated: boolean; } export interface PatchMeta { subject: string; body: string; author: string; email: string; date: string; } export function extractPatchMeta(patch: string): PatchMeta { const lines = patch.split("\n"); let subject = ""; let author = ""; let email = ""; let date = ""; const bodyLines: string[] = []; let inHeaders = true; let pastSubject = false; for (const line of lines) { if (inHeaders) { if (line.startsWith("From: ")) { const match = line.slice(6).match(/^(.*?)\s*<([^>]+)>/); if (match) { author = match[1]!.trim(); email = match[2]!; } else { author = line.slice(6).trim(); } } else if (line.startsWith("Date: ")) { date = line.slice(6).trim(); } else if (line.startsWith("Subject: ")) { subject = line.slice(9).replace(/^\[PATCH[^\]]*\]\s*/, ""); pastSubject = true; } else if (pastSubject && line === "") { inHeaders = false; } } else { if (line === "---") break; bodyLines.push(line); } } while ( bodyLines.length > 0 && bodyLines[bodyLines.length - 1]!.trim() === "" ) { bodyLines.pop(); } return { subject, body: bodyLines.join("\n"), author, email, date }; } export const git = { async init(name: string, branch = "main") { return withRepoLock(name, async () => { const p = repoPath(name); await $`git init --bare --initial-branch=${branch} ${p}`; }); }, async log( name: string, ref = "HEAD", limit = 30, skip = 0, ): Promise { const p = repoPath(name); const sigArgs = [ "-c", "gpg.format=ssh", "-c", `gpg.ssh.allowedSignersFile=${paths.ALLOWED_SIGNERS_PATH}`, ]; try { const out = await $`git ${sigArgs} -C ${p} log ${ref} --format=%H%x1f%s%x1f%an%x1f%ai%x1f%G? --max-count=${limit} --skip=${skip}`.text(); return parseLog(out); } catch { return []; } }, async lsTree( name: string, ref: string, subpath = "", ): Promise { const p = repoPath(name); try { const args = subpath ? [ "git", "-C", p, "ls-tree", "--long", ref, "--", `${subpath}/`, ] : ["git", "-C", p, "ls-tree", "--long", ref]; const out = await $`${args}`.text(); const entries = parseLsTree(out); if (subpath) { // git ls-tree returns full paths like "subpath/name" — strip the prefix const prefix = `${subpath}/`; return entries.map((e) => ({ ...e, name: e.name.startsWith(prefix) ? e.name.slice(prefix.length) : e.name, })); } return entries; } catch { return []; } }, async show( name: string, ref: string, filePath: string, ): Promise { const p = repoPath(name); try { const buf = await $`git -C ${p} show ${`${ref}:${filePath}`}`.arrayBuffer(); return Buffer.from(buf); } catch { return null; } }, async diff(name: string, sha: string): Promise { const p = repoPath(name); try { return await $`git -C ${p} diff-tree --no-commit-id -r -p -M --root ${sha}`.text(); } catch { return ""; } }, async blobSize(name: string, hash: string): Promise { if (/^0+$/.test(hash)) return 0; const p = repoPath(name); try { const out = await $`git -C ${p} cat-file -s ${hash}`.text(); return parseInt(out.trim(), 10) || 0; } catch { return 0; } }, async branches(name: string): Promise { const now = Date.now(); const cached = branchCache.get(name); if (cached && cached.expiresAt > now) return cached.value; const p = repoPath(name); try { // %(refname:short) must be a variable — Bun Shell parses bare `()` as subshell syntax const fmt = "%(refname:short)"; const out = await $`git -C ${p} branch --format=${fmt}`.text(); const value = out.split("\n").filter(Boolean); branchCache.set(name, { value, expiresAt: now + REF_CACHE_TTL_MS }); if (branchCache.size > MAX_BRANCH_CACHE) { branchCache.delete(branchCache.keys().next().value!); } return value; } catch { return []; } }, async tags(name: string): Promise { const now = Date.now(); const cached = tagCache.get(name); if (cached && cached.expiresAt > now) return cached.value; const p = repoPath(name); try { const fmt = "%(refname:short)"; const out = await $`git -C ${p} for-each-ref --format=${fmt} refs/tags/`.text(); const value = out.split("\n").filter(Boolean); tagCache.set(name, { value, expiresAt: now + REF_CACHE_TTL_MS }); if (tagCache.size > MAX_TAG_CACHE) { tagCache.delete(tagCache.keys().next().value!); } return value; } catch { return []; } }, async branchesWithInfo( name: string, maxCount = 1000, ): Promise { const p = repoPath(name); try { // Use actual unit separator byte (\x1f) — git for-each-ref does not // support the %x1f hex escape (that is a git-log pretty-format feature). const sep = "\x1f"; const fmt = `%(refname:short)${sep}%(objectname:short)${sep}%(contents:subject)${sep}%(authorname)${sep}%(authordate:iso8601)`; const out = await $`git -C ${p} for-each-ref --sort=-creatordate --count=${maxCount} --format=${fmt} refs/heads/`.text(); return out .split("\n") .filter(Boolean) .map((line) => { const parts = line.split(sep); return { name: parts[0] ?? "", shortHash: parts[1] ?? "", subject: parts[2] ?? "", authorName: parts[3] ?? "", date: parts[4] ?? "", }; }); } catch { return []; } }, async tagsWithInfo(name: string, maxCount = 1000): Promise { const p = repoPath(name); try { // Use actual unit separator byte (\x1f) — git for-each-ref does not // support the %x1f hex escape (that is a git-log pretty-format feature). // %(*objectname:short) is the dereferenced commit for annotated tags; empty for lightweight. const sep = "\x1f"; const fmt = `%(refname:short)${sep}%(*objectname:short)${sep}%(objectname:short)${sep}%(contents:subject)${sep}%(taggername)${sep}%(creatordate:iso8601)`; const out = await $`git -C ${p} for-each-ref --sort=-creatordate --count=${maxCount} --format=${fmt} refs/tags/`.text(); return out .split("\n") .filter(Boolean) .map((line) => { const parts = line.split(sep); const derefHash = (parts[1] ?? "").trim(); const ownHash = (parts[2] ?? "").trim(); const isAnnotated = derefHash.length > 0; return { name: parts[0] ?? "", shortHash: isAnnotated ? derefHash : ownHash, subject: parts[3] ?? "", taggerName: parts[4] ?? "", date: parts[5] ?? "", isAnnotated, }; }); } catch { return []; } }, async defaultBranch(name: string): Promise { const p = repoPath(name); try { const branches = await git.branches(name); // Read what HEAD points to (may be an unborn branch). let headBranch: string | null = null; try { const out = await $`git -C ${p} symbolic-ref --short HEAD`.text(); headBranch = out.trim(); } catch { // detached HEAD — fall through } // Only trust HEAD if it names a branch that actually exists. if (headBranch && branches.includes(headBranch)) { return headBranch; } // HEAD points to an unborn branch or is detached — prefer "main", // then "master", then whatever branch exists first. return ( branches.find((b) => b === "main") ?? branches.find((b) => b === "master") ?? branches[0] ?? "main" ); } catch { return "main"; } }, async getFileSize( name: string, ref: string, filePath: string, ): Promise { const p = repoPath(name); try { const out = await $`git -C ${p} cat-file -s ${`${ref}:${filePath}`}`.text(); return parseInt(out.trim(), 10); } catch { return null; } }, async checkPatch( name: string, patchContent: string, ): Promise<{ clean: boolean; output: string }> { const p = repoPath(name); const tmpFile = `/tmp/hf-patch-${Date.now()}-${Math.random().toString(36).slice(2)}.patch`; try { await Bun.write(tmpFile, patchContent); // Bare repos have no working tree; populate the index from HEAD so we can // check against git objects (--cached) rather than the filesystem. await $`git -C ${p} read-tree HEAD`.quiet(); const result = await $`git -C ${p} apply --check --cached ${tmpFile}` .quiet() .nothrow(); return { clean: result.exitCode === 0, output: result.stderr.toString(), }; } catch (e) { return { clean: false, output: String(e) }; } finally { await $`rm -f ${tmpFile}`.quiet().nothrow(); } }, async applyPatch( name: string, patchContent: string, authorName: string, authorEmail: string, committerName: string, committerEmail: string, ): Promise { return withRepoLock(name, async () => { const p = repoPath(name); const tmpFile = `/tmp/hf-patch-${Date.now()}-${Math.random().toString(36).slice(2)}.patch`; try { await Bun.write(tmpFile, patchContent); // Populate index, apply to index, then create a real commit in the bare repo. await $`git -C ${p} read-tree HEAD`; await $`git -C ${p} apply --cached ${tmpFile}`; const tree = (await $`git -C ${p} write-tree`.text()).trim(); const parent = ( await $`git -C ${p} rev-parse HEAD`.text() ).trim(); const msg = extractPatchSubject(patchContent); const sigArgs = [ "-c", "gpg.format=ssh", "-c", `user.signingKey=${paths.SSH_HOST_KEY_PATH}`, ]; const commit = ( await $`git ${sigArgs} -C ${p} commit-tree -S ${tree} -p ${parent} -m ${msg}` .env({ ...gitEnv, GIT_AUTHOR_NAME: authorName, GIT_AUTHOR_EMAIL: authorEmail, GIT_COMMITTER_NAME: committerName, GIT_COMMITTER_EMAIL: committerEmail, }) .text() ).trim(); const ref = ( await $`git -C ${p} symbolic-ref HEAD`.text() ).trim(); await $`git -C ${p} update-ref ${ref} ${commit}`; } finally { await $`rm -f ${tmpFile}`.quiet().nothrow(); } }); }, async editFile( name: string, branch: string, filePath: string, content: string, message: string, committerName: string, committerEmail: string, newPath?: string, ): Promise { return withRepoLock(name, async () => { const targetPath = newPath && newPath !== filePath ? newPath : filePath; const isMove = targetPath !== filePath; const p = repoPath(name); const tmpFile = `/tmp/hf-edit-${Date.now()}-${Math.random().toString(36).slice(2)}`; try { await Bun.write(tmpFile, content); if (isMove) { await $`git --work-tree=/tmp -C ${p} read-tree refs/heads/${branch}`; } else { await $`git -C ${p} read-tree refs/heads/${branch}`; } const blobHash = ( await $`git -C ${p} hash-object -w ${tmpFile}`.text() ).trim(); if (isMove) { await $`git --work-tree=/tmp -C ${p} update-index --remove ${filePath}`; } await $`git -C ${p} update-index --add --cacheinfo 100644,${blobHash},${targetPath}`; const tree = isMove ? ( await $`git --work-tree=/tmp -C ${p} write-tree`.text() ).trim() : (await $`git -C ${p} write-tree`.text()).trim(); const parent = ( await $`git -C ${p} rev-parse refs/heads/${branch}`.text() ).trim(); const sigArgs = [ "-c", "gpg.format=ssh", "-c", `user.signingKey=${paths.SSH_HOST_KEY_PATH}`, ]; const commit = ( await $`git ${sigArgs} -C ${p} commit-tree -S ${tree} -p ${parent} -m ${message}` .env({ ...gitEnv, GIT_AUTHOR_NAME: committerName, GIT_AUTHOR_EMAIL: committerEmail, GIT_COMMITTER_NAME: committerName, GIT_COMMITTER_EMAIL: committerEmail, }) .text() ).trim(); await $`git -C ${p} update-ref refs/heads/${branch} ${commit}`; return commit; } finally { await $`rm -f ${tmpFile}`.quiet().nothrow(); } }); }, async createFile( name: string, branch: string, filePath: string, content: string, message: string, committerName: string, committerEmail: string, ): Promise { return withRepoLock(name, async () => { const p = repoPath(name); const tmpFile = `/tmp/hf-new-${Date.now()}-${Math.random().toString(36).slice(2)}`; try { await Bun.write(tmpFile, content); const parentSha = await git.resolveRef( name, `refs/heads/${branch}`, ); if (parentSha) { await $`git -C ${p} read-tree refs/heads/${branch}`; } const blobHash = ( await $`git -C ${p} hash-object -w ${tmpFile}`.text() ).trim(); await $`git -C ${p} update-index --add --cacheinfo 100644,${blobHash},${filePath}`; const tree = (await $`git -C ${p} write-tree`.text()).trim(); const sigArgs = [ "-c", "gpg.format=ssh", "-c", `user.signingKey=${paths.SSH_HOST_KEY_PATH}`, ]; const commitEnv = { ...gitEnv, GIT_AUTHOR_NAME: committerName, GIT_AUTHOR_EMAIL: committerEmail, GIT_COMMITTER_NAME: committerName, GIT_COMMITTER_EMAIL: committerEmail, }; const commit = parentSha ? ( await $`git ${sigArgs} -C ${p} commit-tree -S ${tree} -p ${parentSha} -m ${message}` .env(commitEnv) .text() ).trim() : ( await $`git ${sigArgs} -C ${p} commit-tree -S ${tree} -m ${message}` .env(commitEnv) .text() ).trim(); await $`git -C ${p} update-ref refs/heads/${branch} ${commit}`; return commit; } finally { await $`rm -f ${tmpFile}`.quiet().nothrow(); } }); }, async deleteFile( name: string, branch: string, filePath: string, message: string, committerName: string, committerEmail: string, ): Promise { return withRepoLock(name, async () => { const p = repoPath(name); // --work-tree=/tmp is needed because bare repos have no work tree and // `update-index --remove` requires one (even though it only touches the index). await $`git --work-tree=/tmp -C ${p} read-tree refs/heads/${branch}`; await $`git --work-tree=/tmp -C ${p} update-index --remove ${filePath}`; const tree = ( await $`git --work-tree=/tmp -C ${p} write-tree`.text() ).trim(); const parent = ( await $`git -C ${p} rev-parse refs/heads/${branch}`.text() ).trim(); const sigArgs = [ "-c", "gpg.format=ssh", "-c", `user.signingKey=${paths.SSH_HOST_KEY_PATH}`, ]; const commit = ( await $`git ${sigArgs} -C ${p} commit-tree -S ${tree} -p ${parent} -m ${message}` .env({ ...gitEnv, GIT_AUTHOR_NAME: committerName, GIT_AUTHOR_EMAIL: committerEmail, GIT_COMMITTER_NAME: committerName, GIT_COMMITTER_EMAIL: committerEmail, }) .text() ).trim(); await $`git -C ${p} update-ref refs/heads/${branch} ${commit}`; return commit; }); }, async moveFile( name: string, branch: string, oldPath: string, newPath: string, message: string, committerName: string, committerEmail: string, ): Promise { return withRepoLock(name, async () => { const p = repoPath(name); const tmpFile = `/tmp/hf-move-${Date.now()}-${Math.random().toString(36).slice(2)}`; try { const contentBuf = await $`git -C ${p} show ${`${branch}:${oldPath}`}`.arrayBuffer(); await Bun.write(tmpFile, contentBuf); // --work-tree=/tmp is needed because bare repos have no work tree and // `update-index --remove` requires one (even though it only touches the index). await $`git --work-tree=/tmp -C ${p} read-tree refs/heads/${branch}`; const blobHash = ( await $`git -C ${p} hash-object -w ${tmpFile}`.text() ).trim(); await $`git --work-tree=/tmp -C ${p} update-index --remove ${oldPath}`; await $`git -C ${p} update-index --add --cacheinfo 100644,${blobHash},${newPath}`; const tree = ( await $`git --work-tree=/tmp -C ${p} write-tree`.text() ).trim(); const parent = ( await $`git -C ${p} rev-parse refs/heads/${branch}`.text() ).trim(); const sigArgs = [ "-c", "gpg.format=ssh", "-c", `user.signingKey=${paths.SSH_HOST_KEY_PATH}`, ]; const commit = ( await $`git ${sigArgs} -C ${p} commit-tree -S ${tree} -p ${parent} -m ${message}` .env({ ...gitEnv, GIT_AUTHOR_NAME: committerName, GIT_AUTHOR_EMAIL: committerEmail, GIT_COMMITTER_NAME: committerName, GIT_COMMITTER_EMAIL: committerEmail, }) .text() ).trim(); await $`git -C ${p} update-ref refs/heads/${branch} ${commit}`; return commit; } finally { await $`rm -f ${tmpFile}`.quiet().nothrow(); } }); }, async createTag( repoName: string, tagName: string, ref: string, message?: string, taggerName?: string, taggerEmail?: string, ): Promise<"ok" | "already_exists" | "bad_ref" | "error"> { return withRepoLock(repoName, async () => { const p = repoPath(repoName); const sigArgs = [ "-c", "gpg.format=ssh", "-c", `user.signingKey=${paths.SSH_HOST_KEY_PATH}`, ]; const result = message !== undefined ? await $`git ${sigArgs} -C ${p} tag -s ${tagName} ${ref} -m ${message}` .env({ ...gitEnv, GIT_COMMITTER_NAME: taggerName!, GIT_COMMITTER_EMAIL: taggerEmail!, }) .nothrow() : await $`git -C ${p} tag ${tagName} ${ref}`.nothrow(); if (result.exitCode === 0) { invalidateRefCache(repoName); return "ok"; } const stderr = result.stderr.toString(); if (stderr.includes("already exists")) return "already_exists"; if ( stderr.includes("not a valid object name") || stderr.includes("unknown revision") || stderr.includes("ambiguous argument") ) return "bad_ref"; return "error"; }); }, async createBranch( name: string, branchName: string, sourceRef: string, ): Promise<"ok" | "already_exists" | "bad_ref" | "error"> { return withRepoLock(name, async () => { const p = repoPath(name); try { const sha = await git.resolveRef(name, sourceRef); if (!sha) return "bad_ref"; const exists = await git.resolveRef( name, `refs/heads/${branchName}`, ); if (exists) return "already_exists"; await $`git -C ${p} update-ref refs/heads/${branchName} ${sha}`; invalidateRefCache(name); return "ok"; } catch { return "error"; } }); }, async deleteBranch( name: string, branchName: string, ): Promise<"ok" | "not_found" | "error"> { return withRepoLock(name, async () => { const p = repoPath(name); try { const exists = await git.resolveRef( name, `refs/heads/${branchName}`, ); if (!exists) return "not_found"; await $`git -C ${p} update-ref -d refs/heads/${branchName}`; invalidateRefCache(name); return "ok"; } catch { return "error"; } }); }, async renameBranch( name: string, oldName: string, newName: string, ): Promise<"ok" | "not_found" | "already_exists" | "error"> { return withRepoLock(name, async () => { const p = repoPath(name); try { const sha = await git.resolveRef(name, `refs/heads/${oldName}`); if (!sha) return "not_found"; const exists = await git.resolveRef( name, `refs/heads/${newName}`, ); if (exists) return "already_exists"; await $`git -C ${p} update-ref refs/heads/${newName} ${sha}`; await $`git -C ${p} update-ref -d refs/heads/${oldName}`; invalidateRefCache(name); return "ok"; } catch { return "error"; } }); }, async deleteTag( name: string, tagName: string, ): Promise<"ok" | "not_found" | "error"> { return withRepoLock(name, async () => { const p = repoPath(name); try { const exists = await git.resolveRef( name, `refs/tags/${tagName}`, ); if (!exists) return "not_found"; await $`git -C ${p} tag -d ${tagName}`; invalidateRefCache(name); return "ok"; } catch { return "error"; } }); }, async setHead(name: string, branch: string): Promise { const p = repoPath(name); await $`git -C ${p} symbolic-ref HEAD refs/heads/${branch}`; }, async resolveRef(name: string, ref: string): Promise { const p = repoPath(name); try { const out = await $`git -C ${p} rev-parse --verify ${ref}`.text(); return out.trim() || null; } catch { return null; } }, async hasCommits(name: string): Promise { const p = repoPath(name); try { const out = await $`git -C ${p} log --oneline -1`.quiet().text(); return out.trim().length > 0; } catch { return false; } }, async commitMeta(name: string, sha: string): Promise { const p = repoPath(name); const sigArgs = [ "-c", "gpg.format=ssh", "-c", `gpg.ssh.allowedSignersFile=${paths.ALLOWED_SIGNERS_PATH}`, ]; try { const [metaOut, msgOut] = await Promise.all([ $`git ${sigArgs} -C ${p} show --no-patch --format=%H%x1f%an%x1f%ae%x1f%ai%x1f%cn%x1f%ce%x1f%ci%x1f%P%x1f%G? ${sha}`.text(), $`git -C ${p} log --format=%B -1 ${sha}`.text(), ]); const parts = metaOut.trim().split("\x1f"); const fullMsg = msgOut.trimEnd(); const firstNl = fullMsg.indexOf("\n"); const subject = firstNl >= 0 ? fullMsg.slice(0, firstNl) : fullMsg; const body = firstNl >= 0 ? fullMsg .slice(firstNl + 1) .trimStart() .trimEnd() : ""; return { hash: parts[0] ?? sha, subject, body, author: parts[1] ?? "", email: parts[2] ?? "", date: parts[3] ?? "", committer: parts[4] ?? "", committerEmail: parts[5] ?? "", committerDate: parts[6] ?? "", parents: (parts[7] ?? "").trim().split(/\s+/).filter(Boolean), sigStatus: parseSigStatus(parts[8] ?? ""), }; } catch { return null; } }, };