import { execFileSync } from "node:child_process"; import { resolve } from "node:path"; import type { LibSQLDatabase } from "drizzle-orm/libsql"; import { snapshots } from "./schema.ts"; const GIT_TIMEOUT_MS = 15_000; function workspaceRoot(): string { const raw = process.env.GIT_WORKSPACE_ROOT; return raw ? resolve(raw) : resolve(process.cwd()); } function gitErrorMessage(err: unknown): string { if (err instanceof Error) { const m = err.message.trim(); return m.length > 200 ? `${m.slice(0, 197)}...` : m; } return String(err); } function runGit(cwd: string, args: string[]): string { return execFileSync("git", args, { cwd, encoding: "utf8", timeout: GIT_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024, }).trimEnd(); } function countPorcelainLines(output: string): number { if (!output) return 0; return output.split("\n").filter((line) => line.length > 0).length; } export async function compute(db: LibSQLDatabase, _peers: unknown) { const root = workspaceRoot(); const ts = Date.now(); let branch = ""; let headShort = ""; let porcelainLines = 0; let hasUpstream = 0; let aheadCount = 0; let behindCount = 0; let gitError = ""; try { const inside = runGit(root, ["rev-parse", "--is-inside-work-tree"]).trim(); if (inside !== "true") { gitError = "not a git work tree"; await db.insert(snapshots).values({ ts, branch, headShort, porcelainLines, hasUpstream, aheadCount, behindCount, gitError, }); return { workspaceRoot: root, branch, headShort, porcelainLines, hasUpstream: false, aheadCount, behindCount, gitError, }; } branch = runGit(root, ["rev-parse", "--abbrev-ref", "HEAD"]); headShort = runGit(root, ["rev-parse", "--short", "HEAD"]); porcelainLines = countPorcelainLines(runGit(root, ["status", "--porcelain"])); try { runGit(root, ["rev-parse", "--abbrev-ref", "@{upstream}"]); hasUpstream = 1; const lb = runGit(root, ["rev-list", "--left-right", "--count", "HEAD...@{upstream}"]); const parts = lb.split(/[\t\s]+/).filter(Boolean); if (parts.length >= 2) { aheadCount = Number.parseInt(parts[0], 10) || 0; behindCount = Number.parseInt(parts[1], 10) || 0; } } catch { hasUpstream = 0; aheadCount = 0; behindCount = 0; } } catch (e) { gitError = gitErrorMessage(e); } await db.insert(snapshots).values({ ts, branch, headShort, porcelainLines, hasUpstream, aheadCount, behindCount, gitError, }); return { workspaceRoot: root, branch, headShort, porcelainLines, hasUpstream: hasUpstream === 1, aheadCount, behindCount, gitError: gitError || undefined, }; }