import { createServerFn } from "@tanstack/react-start"; import { execFileSync } from "node:child_process"; const REPO_PATH = process.env.GITUI_REPO_PATH || process.cwd(); function git(args: string[]): string { return execFileSync("git", args, { cwd: REPO_PATH, encoding: "utf-8", maxBuffer: 10 * 1024 * 1024, }).trim(); } export interface CommitInfo { hash: string; shortHash: string; author: string; date: string; message: string; } export interface GitStatus { staged: Array<{ status: string; path: string }>; unstaged: Array<{ status: string; path: string }>; untracked: string[]; } export const getLog = createServerFn({ method: "POST" }) .inputValidator((d: { count?: number; skip?: number }) => d) .handler(async ({ data }) => { const count = data.count ?? 50; const skip = data.skip ?? 0; const raw = git(["log", "--format=%H%x00%h%x00%an%x00%ai%x00%s", "-n", String(count), `--skip=${skip}`]); if (!raw) return [] as CommitInfo[]; return raw.split("\n").map((line): CommitInfo => { const [hash, shortHash, author, date, message] = line.split("\x00"); return { hash, shortHash, author, date, message }; }); }); export const getTree = createServerFn({ method: "POST" }) .inputValidator((d: { ref?: string }) => d) .handler(async ({ data }) => { const ref = data.ref || "HEAD"; const raw = git(["ls-tree", "-r", "--name-only", ref]); return raw.split("\n").filter(Boolean); }); export const getFileContent = createServerFn({ method: "POST" }) .inputValidator((d: { ref: string; path: string }) => d) .handler(async ({ data }) => { return git(["show", `${data.ref}:${data.path}`]); }); export const getDiff = createServerFn({ method: "POST" }) .inputValidator((d: string) => d) .handler(async ({ data: hash }) => { return git(["show", "--format=", "--patch", hash]); }); export const getWorkingDiff = createServerFn().handler(async () => { return git(["diff"]); }); export const getStagedDiff = createServerFn().handler(async () => { return git(["diff", "--cached"]); }); export const getStatus = createServerFn().handler(async (): Promise => { const raw = git(["status", "--porcelain"]); const staged: GitStatus["staged"] = []; const unstaged: GitStatus["unstaged"] = []; const untracked: string[] = []; for (const line of raw.split("\n").filter(Boolean)) { const x = line[0]; const y = line[1]; const path = line.slice(3); if (x === "?") { untracked.push(path); } else { if (x !== " " && x !== "?") { staged.push({ status: x, path }); } if (y !== " " && y !== "?") { unstaged.push({ status: y, path }); } } } return { staged, unstaged, untracked }; }); export const getBranches = createServerFn().handler(async () => { const current = git(["branch", "--show-current"]); const all = git(["branch", "--format=%(refname:short)"]).split("\n").filter(Boolean); return { current, all }; }); export const getCommitDetail = createServerFn({ method: "POST" }) .inputValidator((d: string) => d) .handler(async ({ data: hash }) => { const info = git(["show", "--format=%H%x00%h%x00%an%x00%ae%x00%ai%x00%B", "--stat", hash]); const [header, ...rest] = info.split("\n\n"); const [h, shortHash, author, email, date, ...msgParts] = header.split("\x00"); const message = msgParts.join("\x00"); const stats = rest.join("\n\n"); return { hash: h, shortHash, author, email, date, message, stats }; });