157 lines
3.3 KiB
JavaScript
157 lines
3.3 KiB
JavaScript
import { execFileSync } from "node:child_process";
|
|
import { basename } from "node:path";
|
|
|
|
let cache = null;
|
|
let lastFetch = 0;
|
|
function getRepoName(cwd) {
|
|
try {
|
|
const root = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
cwd,
|
|
encoding: "utf-8",
|
|
stdio: ["pipe", "pipe", "ignore"],
|
|
timeout: 1500,
|
|
}).trim();
|
|
return root ? basename(root) : basename(cwd) || null;
|
|
} catch {
|
|
return basename(cwd) || null;
|
|
}
|
|
}
|
|
function getLastCommit(cwd) {
|
|
try {
|
|
const raw = execFileSync("git", ["log", "-1", "--format=%cr|%s"], {
|
|
cwd,
|
|
encoding: "utf-8",
|
|
stdio: ["pipe", "pipe", "ignore"],
|
|
timeout: 1500,
|
|
}).trim();
|
|
const sep = raw.indexOf("|");
|
|
if (sep > 0) {
|
|
return {
|
|
timeAgo: raw.slice(0, sep).replace(/ ago$/, ""),
|
|
message: raw.slice(sep + 1),
|
|
};
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
return null;
|
|
}
|
|
function getDiffStats(cwd) {
|
|
try {
|
|
const raw = execFileSync("git", ["diff", "--stat"], {
|
|
cwd,
|
|
encoding: "utf-8",
|
|
stdio: ["pipe", "pipe", "ignore"],
|
|
timeout: 1500,
|
|
});
|
|
let added = 0;
|
|
let deleted = 0;
|
|
let modified = 0;
|
|
for (const line of raw.split("\n")) {
|
|
const m = line.match(/(\d+) insertion|\+(\d+)\/-(\d+)/);
|
|
if (m) {
|
|
const a = parseInt(m[1] || m[2] || "0", 10);
|
|
const d = parseInt(m[3] || "0", 10);
|
|
if (a) added += a;
|
|
if (d) deleted += d;
|
|
if (a || d) modified++;
|
|
}
|
|
}
|
|
return { added, deleted, modified };
|
|
} catch {
|
|
return { added: 0, deleted: 0, modified: 0 };
|
|
}
|
|
}
|
|
export function refreshGitStatus(cwd) {
|
|
const now = Date.now();
|
|
if (now - lastFetch < 400 && cache) return cache;
|
|
lastFetch = now;
|
|
const repo = getRepoName(cwd);
|
|
let branch = null;
|
|
try {
|
|
branch =
|
|
execFileSync("git", ["branch", "--show-current"], {
|
|
cwd,
|
|
encoding: "utf-8",
|
|
stdio: ["pipe", "pipe", "ignore"],
|
|
timeout: 1500,
|
|
}).trim() || null;
|
|
} catch {
|
|
cache = {
|
|
repo,
|
|
branch: null,
|
|
dirty: false,
|
|
untracked: false,
|
|
ahead: 0,
|
|
behind: 0,
|
|
added: 0,
|
|
deleted: 0,
|
|
modified: 0,
|
|
lastCommit: null,
|
|
};
|
|
return cache;
|
|
}
|
|
try {
|
|
const status = execFileSync("git", ["status", "--porcelain"], {
|
|
cwd,
|
|
encoding: "utf-8",
|
|
stdio: ["pipe", "pipe", "ignore"],
|
|
timeout: 1500,
|
|
});
|
|
const lines = status.split("\n").filter((l) => l.length > 2);
|
|
const dirty = lines.some((l) => {
|
|
const x = l[0] ?? " ";
|
|
const y = l[1] ?? " ";
|
|
return (x !== "?" && x !== " " && x !== "!") || (y !== " " && y !== "?");
|
|
});
|
|
const untracked = lines.some((l) => l.startsWith("??"));
|
|
let ahead = 0;
|
|
let behind = 0;
|
|
try {
|
|
const ab = execFileSync(
|
|
"git",
|
|
["rev-list", "--left-right", "--count", "HEAD...@{u}"],
|
|
{
|
|
cwd,
|
|
encoding: "utf-8",
|
|
stdio: ["pipe", "pipe", "ignore"],
|
|
timeout: 1500,
|
|
},
|
|
).trim();
|
|
const [a, b] = ab.split("\t").map((n) => parseInt(n, 10));
|
|
ahead = Number.isNaN(a) ? 0 : a;
|
|
behind = Number.isNaN(b) ? 0 : b;
|
|
} catch {
|
|
/* no upstream */
|
|
}
|
|
const diff = getDiffStats(cwd);
|
|
const lastCommit = getLastCommit(cwd);
|
|
cache = {
|
|
repo,
|
|
branch,
|
|
dirty,
|
|
untracked,
|
|
ahead,
|
|
behind,
|
|
...diff,
|
|
lastCommit,
|
|
};
|
|
} catch {
|
|
cache = {
|
|
repo,
|
|
branch,
|
|
dirty: false,
|
|
untracked: false,
|
|
ahead: 0,
|
|
behind: 0,
|
|
added: 0,
|
|
deleted: 0,
|
|
modified: 0,
|
|
lastCommit: getLastCommit(cwd),
|
|
};
|
|
}
|
|
return cache;
|
|
}
|
|
export function invalidateGitStatus() {
|
|
lastFetch = 0;
|
|
}
|