117 lines
2.8 KiB
TypeScript
117 lines
2.8 KiB
TypeScript
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,
|
|
};
|
|
}
|