Persist base branch for Paseo worktrees and gate merge action

This commit is contained in:
Mohamed Boudra
2026-01-23 15:27:42 +07:00
parent 23cd462336
commit eab8713c24
15 changed files with 565 additions and 79 deletions
@@ -0,0 +1,87 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { isAbsolute, join, resolve } from "path";
import { z } from "zod";
const PaseoWorktreeMetadataSchema = z.object({
version: z.literal(1),
baseRefName: z.string().min(1),
});
export type PaseoWorktreeMetadata = z.infer<typeof PaseoWorktreeMetadataSchema>;
function getGitDirForWorktreeRoot(worktreeRoot: string): string {
const gitPath = join(worktreeRoot, ".git");
if (!existsSync(gitPath)) {
throw new Error(`Not a git repository: ${worktreeRoot}`);
}
// In a worktree checkout, `.git` is a file containing `gitdir: <path>`.
// In a normal checkout, `.git` is a directory.
try {
const gitFileContent = readFileSync(gitPath, "utf8");
const match = gitFileContent.match(/gitdir:\s*(.+)/);
if (match?.[1]) {
const raw = match[1].trim();
return isAbsolute(raw) ? raw : resolve(worktreeRoot, raw);
}
} catch {
// If `.git` is a directory, readFileSync will throw; fall through.
}
return gitPath;
}
export function getPaseoWorktreeMetadataPath(worktreeRoot: string): string {
const gitDir = getGitDirForWorktreeRoot(worktreeRoot);
return join(gitDir, "paseo", "worktree.json");
}
export function normalizeBaseRefName(input: string): string {
const trimmed = input.trim();
if (!trimmed) {
throw new Error("Base branch is required");
}
if (trimmed.startsWith("origin/")) {
return trimmed.slice("origin/".length);
}
return trimmed;
}
export function writePaseoWorktreeMetadata(
worktreeRoot: string,
options: { baseRefName: string }
): void {
const baseRefName = normalizeBaseRefName(options.baseRefName);
if (baseRefName === "HEAD") {
throw new Error("Base branch cannot be HEAD");
}
if (baseRefName.includes("..") || baseRefName.includes("@{")) {
throw new Error(`Invalid base branch: ${baseRefName}`);
}
if (!/^[0-9A-Za-z._/-]+$/.test(baseRefName)) {
throw new Error(`Invalid base branch: ${baseRefName}`);
}
const metadataPath = getPaseoWorktreeMetadataPath(worktreeRoot);
mkdirSync(join(getGitDirForWorktreeRoot(worktreeRoot), "paseo"), { recursive: true });
const metadata: PaseoWorktreeMetadata = { version: 1, baseRefName };
writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
}
export function readPaseoWorktreeMetadata(worktreeRoot: string): PaseoWorktreeMetadata | null {
const metadataPath = getPaseoWorktreeMetadataPath(worktreeRoot);
if (!existsSync(metadataPath)) {
return null;
}
const parsed = JSON.parse(readFileSync(metadataPath, "utf8"));
return PaseoWorktreeMetadataSchema.parse(parsed);
}
export function requirePaseoWorktreeBaseRefName(worktreeRoot: string): string {
const metadataPath = getPaseoWorktreeMetadataPath(worktreeRoot);
const metadata = readPaseoWorktreeMetadata(worktreeRoot);
if (!metadata) {
throw new Error(`Missing Paseo worktree base metadata: ${metadataPath}`);
}
return metadata.baseRefName;
}