Archived
Nothing consumed GitWorktreeInfo.locked: a locked worktree is a real checkout and stays a usable workspace, so no caller needs to distinguish it. Unknown porcelain keys are already ignored, so parsing it was speculative. The parser test still feeds a real `locked` line and asserts it is ignored, and the service test still pins that a present, non-prunable worktree is kept, so the policy stays covered without the field.
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
import { sanitizedGitEnv } from "../git/gitEnv.js";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
export interface GitWorktreeInfo {
|
|
path: string;
|
|
branch?: string;
|
|
bare?: boolean;
|
|
detached?: boolean;
|
|
/** Git reports a linked worktree as prunable when its checkout directory no longer exists. */
|
|
prunable?: boolean;
|
|
}
|
|
|
|
export async function isGitRepository(path: string): Promise<boolean> {
|
|
try {
|
|
const { stdout } = await execFileAsync("git", ["-C", path, "rev-parse", "--is-inside-work-tree"], { env: sanitizedGitEnv() });
|
|
return stdout.trim() === "true";
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function discoverGitWorktrees(path: string): Promise<GitWorktreeInfo[]> {
|
|
const { stdout } = await execFileAsync("git", ["-C", path, "worktree", "list", "--porcelain"], { env: sanitizedGitEnv() });
|
|
return parseGitWorktreeList(stdout);
|
|
}
|
|
|
|
/**
|
|
* Parses `git worktree list --porcelain` output into facts only. Deciding which worktrees a
|
|
* project should show (for example hiding prunable ones) is workspace policy, not parsing.
|
|
*/
|
|
export function parseGitWorktreeList(stdout: string): GitWorktreeInfo[] {
|
|
const chunks = stdout.trim().split(/\n\s*\n/).filter(Boolean);
|
|
|
|
return chunks.map((chunk) => {
|
|
const info: GitWorktreeInfo = { path: "" };
|
|
for (const line of chunk.split("\n")) {
|
|
const [key, ...rest] = line.split(" ");
|
|
const value = rest.join(" ");
|
|
if (key === "worktree") info.path = value;
|
|
if (key === "branch") info.branch = value.replace(/^refs\/heads\//, "");
|
|
if (key === "bare") info.bare = true;
|
|
if (key === "detached") info.detached = true;
|
|
if (key === "prunable") info.prunable = true;
|
|
}
|
|
return info;
|
|
}).filter((w) => w.path);
|
|
}
|