95 lines · 3.8 KB
import { waitUntil } from "cloudflare:workers";
import { hasPermission, loadRepoById, repoPermission } from "./repos";
import { loadUserById, toViewer } from "./auth";
import type { RepoRow } from "~/lib/types";
import { checkMergeability, loadPullByIssueId } from "./pulls";
import { scheduleWorkflows } from "./ci/scheduler";
import { requestCodeOwnerReviews } from "./review-requests";
import { emitWebhook, publicUrl } from "./events/emit";
import type { Viewer } from "~/lib/types";

/**
 * Pull requests from forks run untrusted code on our containers, so (like GitHub) runs from actors
 * without write access to the base repository wait for a maintainer to approve them.
 */
export async function forkRunNeedsApproval(
  baseRepo: RepoRow,
  headRepo: RepoRow,
  actorId: string | null,
): Promise<boolean> {
  if (baseRepo.id === headRepo.id) return false;
  const actor = actorId ? await loadUserById(actorId) : null;
  return !hasPermission(await repoPermission(baseRepo, actor ? toViewer(actor) : null), "write");
}

/**
 * The code that runs belongs to the pull request's author and the head repository's owner, not to whoever
 * triggered the event, so a maintainer reopening or editing a fork PR does not implicitly approve its code.
 */
export async function forkPullNeedsApproval(baseRepo: RepoRow, headRepo: RepoRow, authorId: string): Promise<boolean> {
  if (baseRepo.id === headRepo.id) return false;
  if (await forkRunNeedsApproval(baseRepo, headRepo, authorId)) return true;
  return forkRunNeedsApproval(baseRepo, headRepo, headRepo.owner_id);
}

/**
 * Kicks off CI and mergeability checks for a pull request lifecycle event without blocking the request.
 * `expectedHeadSha` is the head the actor saw; if it moved before the background task ran, nothing is scheduled.
 */
export function afterPullChange(
  target: { repo_id: string; issue_id: string },