73 lines · 3.9 KB
import { loadPullByIssueId, MergeError } from "../pulls";
import { hasPermission, loadRepoById, repoPermission } from "../repos";
import { loadProtections, protectionFor } from "../protection";
import { listCommitStatuses, requiredCheckResults } from "../statuses";
import { enqueuePull } from "../merge-queue";
import { actorViewer } from "../merge-automation";
import { ciVerdict, MAX_ENQUEUE_ATTEMPTS } from "./requeue-policy";
import { loadRun, updateRun } from "./store";

export type RequeueStep = "done" | "wait";

/** Ends a requeue that can no longer progress (for example after an unexpected error), so it never shows as waiting. */
export async function abandonRequeue(runId: string, reason: string): Promise<void> {
  await updateRun(runId, ["applied"], { requeue_status: "gave_up", requeue_error: reason });
}

async function giveUp(runId: string, reason: string): Promise<RequeueStep> {
  await updateRun(runId, ["applied"], { requeue_status: "gave_up", requeue_error: reason });
  return "done";
}

/**
 * One poll of a queue-triggered fix: once the fix commit is green the pull goes back into the merge queue
 * as the user who queued it. Red CI, a moved head or repeated enqueue failures end the attempt.
 */
export async function checkRequeue(runId: string, poll: number, final = false): Promise<RequeueStep> {
  const runRow = await loadRun(runId);
  if (!runRow || runRow.status !== "applied" || runRow.requeue_status !== "waiting") return "done";
  if (!runRow.queue_method || !runRow.commit_sha) return giveUp(runId, "Nothing to re-enqueue");
  const pull = await loadPullByIssueId(runRow.repo_id, runRow.pull_issue_id);
  if (!pull || pull.state !== "open") return giveUp(runId, "The pull request is no longer open");
  if (pull.head_sha !== runRow.commit_sha) return giveUp(runId, "The head branch changed after the fix");
  if (pull.draft === 1) return giveUp(runId, "The pull request was converted to a draft");
  const [baseRepo, actor] = await Promise.all([loadRepoById(pull.repo_id), actorViewer(runRow.actor_id)]);
  if (!baseRepo || !actor) return giveUp(runId, "The repository or the user who queued the pull is gone");
  if (!hasPermission(await repoPermission(baseRepo, actor), "write")) {
    return giveUp(runId, "The user who queued the pull can no longer merge it");
  }
  const applierId = runRow.applied_by ?? runRow.actor_id;
  const applier = applierId ? await actorViewer(applierId) : null;