openclaw / src /agents /worktrees /base-ref.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
e249c6d verified
Raw History Blame Contribute Delete
2.86 kB
import { requireGitCommandOutput } from "../../infra/git-exec.js";
import { commandError, runGit } from "./git.js";
type ResolvedWorktreeBase = {
commit: string;
gitOperand: string;
recordRef: string;
remote: boolean;
};
export class InvalidWorktreeBaseRefError extends Error {
constructor(options?: ErrorOptions) {
super(
"Worktree base ref does not resolve to a commit. Choose a local or remote branch and retry.",
options,
);
this.name = "InvalidWorktreeBaseRefError";
}
}
export async function resolveWorktreeBase(
repoRoot: string,
baseRef?: string,
signal?: AbortSignal,
assertCurrent?: () => void,
): Promise<ResolvedWorktreeBase> {
if (baseRef) {
const verified = await runGit(
repoRoot,
[
"-c",
"core.warnAmbiguousRefs=true",
"rev-parse",
"--verify",
"--end-of-options",
`${baseRef === "-" ? "@{-1}" : baseRef}^{commit}`,
],
{ signal, beforeRun: assertCurrent },
);
signal?.throwIfAborted();
if (
verified.termination === "exit" &&
typeof verified.code === "number" &&
verified.code !== 0
) {
throw new InvalidWorktreeBaseRefError({
cause: commandError("git rev-parse --verify", verified),
});
}
const commit = requireGitCommandOutput("git rev-parse --verify", verified).trim();
if (!commit || commit.includes("\n") || verified.stderr.trim()) {
throw new InvalidWorktreeBaseRefError({
cause: commandError("git rev-parse --verify", verified),
});
}
// `worktree add -b` forwards its start point to `git branch`, which parses
// options again without another `--`; pass the verified commit for dashed refs.
const gitOperand = baseRef !== "-" && baseRef.startsWith("-") ? commit : baseRef;
return { commit, gitOperand, recordRef: baseRef, remote: false };
}
const fetched = await runGit(repoRoot, ["fetch", "origin"], { signal, beforeRun: assertCurrent });
signal?.throwIfAborted();
if (fetched.termination === "exit" && fetched.code === 0) {
const remoteHead = await runGit(
repoRoot,
["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
{ signal, beforeRun: assertCurrent },
);
if (remoteHead.termination === "exit" && remoteHead.code === 0 && remoteHead.stdout.trim()) {
const remoteRef = remoteHead.stdout.trim();
try {
const resolved = await resolveWorktreeBase(repoRoot, remoteRef, signal, assertCurrent);
return { ...resolved, remote: true };
} catch (error) {
// Pruning a retired default branch can leave origin/HEAD dangling.
if (!(error instanceof InvalidWorktreeBaseRefError)) {
throw error;
}
}
}
}
return await resolveWorktreeBase(repoRoot, "HEAD", signal, assertCurrent);
}