UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

262 lines (261 loc) • 10.3 kB
import { t as pruneMapToMaxSize } from "./map-size-BlrH_CnE.mjs"; import { i as readGitMetadataFile, n as readGitHead, r as readGitMetadataDirectories } from "./git-root-ChTibfp1.mjs"; import { o as WorktreeRepositoryError } from "./errors-CgO78Orl.mjs"; import { f as requireGitCommandOutput, u as normalizeGitPathForFilesystem } from "./git-exec-BOEpfMeZ.mjs"; import { n as gitFilesystemEnvironmentRevision } from "./git-worker-context-DDPUBONa.mjs"; import { a as insideGitCheckout, d as runGit, n as findGitCheckoutRoot } from "./git-D3obqZyl.mjs"; import { t as resolveCheckoutRootFromRealPath } from "./repository-paths-D7Ye65Xh.mjs"; import fs from "node:fs"; import path from "node:path"; import fs$1 from "node:fs/promises"; //#region src/agents/worktrees/branches.runtime.ts const BRANCH_INVENTORY_MAX_OUTPUT_BYTES = 262144; const BRANCH_SUGGESTIONS_PER_KIND = 100; const branchInventories = /* @__PURE__ */ new Map(); function branchInventoryRevision(repoRoot, requested, configPaths) { const environment = gitFilesystemEnvironmentRevision(); if (environment === void 0 || configPaths.some((file) => !path.isAbsolute(file))) return; try { const stamps = []; const stamp = (file) => { const stat = fs.lstatSync(file, { bigint: true, throwIfNoEntry: false }); if (stat && !stat.isFile() && !stat.isDirectory() || stamps.length >= 1024) throw new Error("Uncacheable ref inventory"); stamps.push([file, stat ? `${stat.dev}:${stat.ino}:${stat.mode}:${stat.uid}:${stat.gid}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}` : "missing"]); return stat; }; stamp(path.join(repoRoot, ".git")); const directories = readGitMetadataDirectories(repoRoot); if (!directories) return; const { gitDir, commonDir: common } = directories; for (let directory = requested; directory !== repoRoot; directory = path.dirname(directory)) if (directory === path.dirname(directory) || !stamp(directory)?.isDirectory()) return; const head = readGitHead(repoRoot, { maxDepth: 1 })?.value; const objects = path.join(common, "objects"); if (!head || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(head) || [ "reftable", "refs/replace", "info/grafts", "objects/info/alternates" ].some((name) => fs.existsSync(path.join(common, name))) || fs.existsSync(path.join(common, "packed-refs")) && readGitMetadataFile(path.join(common, "packed-refs"), Number.MAX_SAFE_INTEGER).toString("utf8").includes("refs/replace/")) return; for (const file of [ repoRoot, gitDir, common, path.join(gitDir, "HEAD"), path.join(gitDir, "commondir"), path.join(common, "packed-refs"), path.join(common, "shallow"), objects, path.join(objects, head.slice(0, 2)), path.join(objects, head.slice(0, 2), head.slice(2)) ]) stamp(file); for (const file of [ ...configPaths, path.join(common, "config"), path.join(gitDir, "config.worktree") ]) { const stat = stamp(file); if (stat && (!stat.isFile() || stat.size > 1048576 || /include/iu.test(readGitMetadataFile(file).toString("utf8").replace(/\\\r?\n/gu, "")))) return; } const packs = path.join(objects, "pack"); if (stamp(packs)?.isDirectory()) { for (const name of fs.readdirSync(packs).toSorted()) if (name.endsWith(".promisor") || !stamp(path.join(packs, name))?.isFile()) return; } const visit = (directory, depth = 0) => { if (depth > 32 || !stamp(directory)?.isDirectory()) throw new Error("Uncacheable ref directory"); for (const name of fs.readdirSync(directory).toSorted()) { const file = path.join(directory, name); if (stamp(file)?.isDirectory()) visit(file, depth + 1); } }; visit(path.join(common, "refs")); const worktreeRefs = path.join(gitDir, "refs"); if (common !== gitDir && stamp(worktreeRefs)?.isDirectory()) visit(worktreeRefs); return { revision: JSON.stringify([ environment, head, stamps ]), head }; } catch { return; } } async function listRepositoryBranchRefs(repoRoot, patterns, count) { const result = await runGit(repoRoot, [ "-c", "core.warnAmbiguousRefs=true", "for-each-ref", ...count === void 0 ? [] : [`--count=${count}`], "--sort=refname", count === void 0 ? "--format=%(refname)%00%(refname:short)%00%(symref)%00%(HEAD)" : "--format=%(refname)%00%(refname:short)", ...patterns ], { maxOutputBytes: BRANCH_INVENTORY_MAX_OUTPUT_BYTES, terminateOnOutputLimit: count === void 0 }); const output = requireGitCommandOutput("git for-each-ref", result); const branches = []; for (const line of output.trim().split("\n")) { const [ref, name, symbolicRef, head] = line.split("\0"); const metadata = { symbolicRef: symbolicRef || void 0, current: head === "*" }; if (!ref || !name) continue; if (ref.startsWith("refs/heads/")) branches.push({ ref, ...metadata, branchName: ref.slice(11), branch: { name, kind: "local" } }); else if (ref.startsWith("refs/remotes/")) { const remoteRef = ref.slice(13); const slash = remoteRef.indexOf("/"); const branchName = slash > 0 ? remoteRef.slice(slash + 1) : ""; branches.push({ ref, branchName, ...metadata, branch: { name, kind: "remote" } }); } } return branches; } async function readRepositoryBranches(repoRoot, options = {}) { let sourceRoot; let requested; let snapshot; let configPaths = []; const environment = gitFilesystemEnvironmentRevision(); try { requested = await fs$1.realpath(repoRoot).catch(() => { throw new Error(`repository does not exist: ${repoRoot}`); }); if (options.includeRepositoryStatus) { if (!(await fs$1.stat(requested)).isDirectory()) return { branches: [], repositoryStatus: "unavailable" }; if (!insideGitCheckout(requested)) return { branches: [], repositoryStatus: "not_git" }; } const candidate = findGitCheckoutRoot(requested); const cached = candidate ? branchInventories.get(candidate) : void 0; if (candidate && environment !== void 0) { if (cached?.environment === environment) configPaths = cached.configPaths; else { const variables = await runGit(requested, ["var", "-l"]); if (variables.code === 0) configPaths = [...variables.stdout.matchAll(/^GIT_CONFIG_(?:SYSTEM|GLOBAL)=(.+)$/gmu)].map((match) => normalizeGitPathForFilesystem(match[1])); } snapshot = configPaths.length ? branchInventoryRevision(candidate, requested, configPaths) : void 0; if (cached?.requested === requested && snapshot?.revision === cached.revision) { branchInventories.delete(candidate); branchInventories.set(candidate, cached); return { ...cached.result, ...options.includeRepositoryStatus ? { repositoryStatus: "git" } : {} }; } } const admitted = await resolveCheckoutRootFromRealPath(requested, repoRoot); sourceRoot = admitted.root; if (candidate !== sourceRoot || snapshot?.head !== admitted.commit) snapshot = void 0; } catch (error) { if (options.includeRepositoryStatus) { if (error instanceof WorktreeRepositoryError && error.reason === "unborn") return { branches: [], repositoryStatus: "not_git" }; return { branches: [], repositoryStatus: "unavailable" }; } throw error; } branchInventories.delete(sourceRoot); let inventory; try { inventory = await listRepositoryBranchRefs(sourceRoot, ["refs/heads/", "refs/remotes/"]); } catch { inventory = void 0; } const branches = /* @__PURE__ */ new Map(); let branchesUnavailable = false; for (const prefix of ["refs/remotes/", "refs/heads/"]) try { const entries = inventory ? inventory.filter((entry) => entry.ref.startsWith(prefix)).slice(0, BRANCH_SUGGESTIONS_PER_KIND) : await listRepositoryBranchRefs(sourceRoot, [prefix], BRANCH_SUGGESTIONS_PER_KIND); for (const entry of entries) if (entry.branchName && (entry.branch.kind !== "remote" || entry.branchName !== "HEAD")) branches.set(entry.branchName, entry); } catch { branchesUnavailable = true; } let defaultRef; let headRef; if (inventory) { defaultRef = inventory.find((entry) => entry.ref === "refs/remotes/origin/HEAD")?.symbolicRef; headRef = inventory.find((entry) => entry.current)?.ref; } else { const remoteHead = await runGit(sourceRoot, [ "symbolic-ref", "--quiet", "refs/remotes/origin/HEAD" ]); defaultRef = remoteHead.code === 0 ? remoteHead.stdout.trim() : void 0; const head = await runGit(sourceRoot, [ "symbolic-ref", "--quiet", "HEAD" ]); headRef = head.code === 0 ? head.stdout.trim() : void 0; } const resolveBranch = async (ref) => { if (!ref) return; const known = (inventory ?? [...branches.values()]).find((entry) => entry.ref === ref); if (known || inventory) return known?.branchName && (known.branch.kind !== "remote" || known.branchName !== "HEAD") ? known : void 0; try { return (await listRepositoryBranchRefs(sourceRoot, [ref], 1)).find((entry) => entry.ref === ref && entry.branchName && (entry.branch.kind !== "remote" || entry.branchName !== "HEAD")); } catch { branchesUnavailable = true; return; } }; const defaultEntry = await resolveBranch(defaultRef?.startsWith("refs/remotes/origin/") ? `refs/heads/${defaultRef.slice(20)}` : void 0) ?? await resolveBranch(defaultRef); const headEntry = await resolveBranch(headRef); for (const entry of [defaultEntry, headEntry]) if (entry) branches.set(entry.branchName, entry); const rank = (entry) => entry.ref === defaultEntry?.ref ? 0 : entry.ref === headEntry?.ref ? 1 : 2; const result = { branches: [...branches.values()].toSorted((a, b) => rank(a) - rank(b) || a.branch.name.localeCompare(b.branch.name)).map((entry) => entry.branch), ...defaultEntry ? { defaultBranch: defaultEntry.branch.name } : {}, ...headEntry ? { headBranch: headEntry.branch.name } : {}, ...branchesUnavailable ? { branchesUnavailable: true } : {} }; if (!branchesUnavailable && environment !== void 0 && snapshot !== void 0 && snapshot.revision === branchInventoryRevision(sourceRoot, requested, configPaths)?.revision) { branchInventories.set(sourceRoot, { revision: snapshot.revision, requested, environment, configPaths, result }); pruneMapToMaxSize(branchInventories, 64); } return { ...result, ...options.includeRepositoryStatus ? { repositoryStatus: "git" } : {} }; } //#endregion export { readRepositoryBranches };