Files
pi-agent-integrated/pi-web/lib/git-status.ts

48 lines
1.7 KiB
TypeScript

import type { GitFileStatus } from "./git-types";
export interface GitPorcelainEntry {
path: string;
originalPath?: string;
indexStatus: string;
worktreeStatus: string;
}
function usesRenamePath(indexStatus: string, worktreeStatus: string): boolean {
return indexStatus === "R" || indexStatus === "C" || worktreeStatus === "R" || worktreeStatus === "C";
}
export function parseGitPorcelainV1(output: string): GitPorcelainEntry[] {
const records = output.split("\0");
const entries: GitPorcelainEntry[] = [];
for (let i = 0; i < records.length; i++) {
const record = records[i];
if (!record || record.length < 4 || record[2] !== " ") continue;
const indexStatus = record[0];
const worktreeStatus = record[1];
const entry: GitPorcelainEntry = {
path: record.slice(3),
indexStatus,
worktreeStatus,
};
if (usesRenamePath(indexStatus, worktreeStatus)) {
entry.originalPath = records[++i] || undefined;
}
entries.push(entry);
}
return entries;
}
const CONFLICT_STATUSES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
export function classifyGitStatus(entry: GitPorcelainEntry): Pick<GitFileStatus, "status" | "code"> {
const pair = `${entry.indexStatus}${entry.worktreeStatus}`;
if (pair === "??") return { status: "untracked", code: "U" };
if (CONFLICT_STATUSES.has(pair) || pair.includes("U")) return { status: "conflict", code: "C" };
if (pair.includes("D")) return { status: "deleted", code: "D" };
if (pair.includes("R") || pair.includes("C")) return { status: "renamed", code: "R" };
if (pair.includes("A")) return { status: "added", code: "A" };
return { status: "modified", code: "M" };
}