fix(FN-4474): harden branch autocorrect shell invocation
Fusion-Task-Id: FN-4474 Fusion-Task-Lineage: 979782e1-c44d-4f35-aded-237bf33d0c9f
This commit is contained in:
@@ -5,18 +5,22 @@ const execAsync = promisify(exec);
|
||||
const GIT_TIMEOUT_MS = 10_000;
|
||||
const GIT_MAX_BUFFER = 1024 * 1024;
|
||||
|
||||
type BranchAutocorrectParams = {
|
||||
export type BranchAutocorrectParams = {
|
||||
worktreePath: string;
|
||||
observedBranch: string;
|
||||
expectedBranch: string;
|
||||
rootDir: string;
|
||||
};
|
||||
|
||||
type BranchAutocorrectResult = {
|
||||
export type BranchAutocorrectResult = {
|
||||
status: "renamed" | "checked-out" | "failed";
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
function quoteShellArg(value: string): string {
|
||||
return `'${value.replace(/'/g, `'\\''`)}'`;
|
||||
}
|
||||
|
||||
async function runGit(command: string, worktreePath: string): Promise<{ ok: true; stdout: string } | { ok: false; reason: string }> {
|
||||
try {
|
||||
const { stdout } = await execAsync(command, {
|
||||
@@ -43,36 +47,40 @@ export async function attemptBranchAutocorrect({
|
||||
expectedBranch,
|
||||
rootDir: _rootDir,
|
||||
}: BranchAutocorrectParams): Promise<BranchAutocorrectResult> {
|
||||
void _rootDir;
|
||||
const observed = observedBranch.trim();
|
||||
const expected = expectedBranch.trim();
|
||||
if (!observed || !expected || observed === expected) {
|
||||
return { status: "failed", reason: "invalid-input" };
|
||||
}
|
||||
|
||||
const upstream = await runGit(`git rev-parse --abbrev-ref --symbolic-full-name ${observed}@{u}`, worktreePath);
|
||||
const observedShaResult = await runGit(`git rev-parse ${observed}`, worktreePath);
|
||||
const observedArg = quoteShellArg(observed);
|
||||
const expectedArg = quoteShellArg(expected);
|
||||
const upstream = await runGit(`git rev-parse --abbrev-ref --symbolic-full-name ${observedArg}@{u}`, worktreePath);
|
||||
const observedShaResult = await runGit(`git rev-parse ${observedArg}`, worktreePath);
|
||||
|
||||
let isFreshBranch = false;
|
||||
if (!upstream.ok && observedShaResult.ok) {
|
||||
const contains = await runGit(`git for-each-ref --format='%(refname:short)' --contains ${observedShaResult.stdout.trim()} refs/heads/`, worktreePath);
|
||||
if (contains.ok) {
|
||||
const refs = contains.stdout
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
isFreshBranch = refs.length === 1 && refs[0] === observed;
|
||||
const observedSha = observedShaResult.stdout.trim();
|
||||
if (/^[0-9a-f]{4,64}$/i.test(observedSha)) {
|
||||
const contains = await runGit(`git for-each-ref --format='%(refname:short)' --contains ${observedSha} refs/heads/`, worktreePath);
|
||||
if (contains.ok) {
|
||||
const refs = contains.stdout
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
isFreshBranch = refs.length === 1 && refs[0] === observed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isFreshBranch) {
|
||||
const rename = await runGit(`git branch -m ${observed} ${expected}`, worktreePath);
|
||||
const rename = await runGit(`git branch -m ${observedArg} ${expectedArg}`, worktreePath);
|
||||
if (rename.ok) {
|
||||
return { status: "renamed" };
|
||||
}
|
||||
}
|
||||
|
||||
const checkout = await runGit(`git checkout -B ${expected}`, worktreePath);
|
||||
const checkout = await runGit(`git checkout -B ${expectedArg}`, worktreePath);
|
||||
if (checkout.ok) {
|
||||
return { status: "checked-out" };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user