import { createServerFn } from '@tanstack/react-start' import { execSync } from 'node:child_process' const REPO_PATH = process.env.GITUI_REPO_PATH || process.cwd() function git(args: string): string { return execSync(`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 ${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 } })