146 lines · 6.9 KB
import { waitUntil } from "cloudflare:workers";
import { all, batch, stmt } from "./db";
import { adjustOpenCounter } from "./issue-counters";
import { repoDb } from "./scoped-db";
import { pullBaseRepoIds } from "./pull-scope";
import { loadUserById, toViewer } from "./auth";
import { pullHeadAccessible } from "./git/bridge-policy";
import { dropHiddenRefsForRepo, dropPullRefsForIssue, requestHiddenRefCleanup } from "./git/pull-bridge";
import { CAPS } from "./limits";
import { addIssueEvent } from "./pulls";
import type { PullRow } from "./pulls";
import { hasPermission, loadRepoById, repoPermission } from "./repos";
import type { RepoRow, Viewer } from "~/lib/types";
import { issuesChanged } from "./repo-state";

type GuardedPull = Pick<PullRow, "issue_id" | "repo_id" | "head_repo_id" | "author_id" | "state">;

/** Closes a pull request because its author can no longer read one of its repositories. */
async function closeForRevokedAccess(pull: GuardedPull): Promise<void> {
  const now = Date.now();
  const closed = await repoDb(pull.repo_id).run(
    "UPDATE issues SET state = 'closed', closed_at = ?, closed_by = NULL, updated_at = ? WHERE id = ? AND state = 'open'",
    now,
    now,
    pull.issue_id,
  );
  if (closed.meta.changes > 0) {
    await adjustOpenCounter({ id: pull.issue_id, repo_id: pull.repo_id, is_pull: 1 }, -1);
  }
  issuesChanged(pull.repo_id, [pull.issue_id]);
  await addIssueEvent(pull, null, "closed", { reason: "access_revoked" });
  waitUntil(dropPullRefsForIssue(pull.repo_id, pull.issue_id));
}

/**
 * Whether a fork pull request may still synchronize, bridge or run CI: the author must currently read
 * both repositories and the head must be a live fork of the base. When that stopped being true the pull
 * request is closed and its hidden refs dropped, so revoked readers stop receiving (and feeding) history.
 */
export async function ensurePullHeadAccessible(pull: GuardedPull): Promise<boolean> {