import type { FileEntry } from "./types"; export async function isGitRepo(): Promise { try { await Bun.$`git rev-parse --is-inside-work-tree`.quiet(); return true; } catch { return false; } } export async function getRepoRoot(): Promise { const result = await Bun.$`git rev-parse --show-toplevel`.quiet().text(); return result.trim(); } function statusToLabel(status: string): string { switch (status[0]) { case "A": return "new"; case "D": return "deleted"; case "R": return "renamed"; case "C": return "copied"; case "M": return "modified"; case "T": return "type change"; default: return "modified"; } } function parseNameStatus(output: string): FileEntry[] { return output .trim() .split("\n") .filter(Boolean) .map((line) => { const [status, ...pathParts] = line.split("\t"); const path = pathParts[pathParts.length - 1] ?? ""; return { path, status: status!, label: statusToLabel(status!) }; }); } export async function getStagedFiles(): Promise { try { const result = await Bun.$`git diff --cached --name-status`.quiet().text(); return parseNameStatus(result); } catch { return []; } } export async function getUnstagedFiles(): Promise { const files: FileEntry[] = []; try { const result = await Bun.$`git diff --name-status`.quiet().text(); files.push(...parseNameStatus(result)); } catch {} try { const result = await Bun.$`git ls-files --others --exclude-standard`.quiet().text(); for (const path of result.trim().split("\n").filter(Boolean)) { files.push({ path, status: "??", label: "new" }); } } catch {} return files; } export async function getStagedDiff(): Promise { try { const result = await Bun.$`git diff --staged`.quiet().text(); return result.trim(); } catch { return ""; } } export async function getRecentCommits(count = 10): Promise { try { const n = String(count); const result = await Bun.$`git log --oneline -n ${n}`.quiet().text(); return result.trim().split("\n").filter(Boolean); } catch { return []; } } export async function stageFiles(paths: string[]): Promise { if (paths.length === 0) return; await Bun.$`git add -- ${paths}`; } export async function commit( message: string, ): Promise<{ branch: string; hash: string; files: number; insertions: number; deletions: number }> { const proc = Bun.spawn(["git", "commit", "-m", message], { stdout: "pipe", stderr: "pipe", }); const exitCode = await proc.exited; const stdout = await new Response(proc.stdout).text(); const stderr = await new Response(proc.stderr).text(); if (exitCode !== 0) { throw new Error(stderr.trim() || `git commit failed (exit code ${exitCode})`); } const branchHashMatch = stdout.match(/\[(\S+)\s+([0-9a-f]{7,})/); const branch = branchHashMatch?.[1] ?? ""; const hash = branchHashMatch?.[2] ?? ""; const filesMatch = stdout.match(/(\d+)\s+file/); const insertionsMatch = stdout.match(/(\d+)\s+insertion/); const deletionsMatch = stdout.match(/(\d+)\s+deletion/); return { branch, hash, files: parseInt(filesMatch?.[1] ?? "0"), insertions: parseInt(insertionsMatch?.[1] ?? "0"), deletions: parseInt(deletionsMatch?.[1] ?? "0"), }; }