49 lines · 2.2 KB
import { loadUserById, toViewer } from "../auth";
import { hasPermission, repoPermission } from "../repos";
import type { RepoRow } from "~/lib/types";

/** Copying must never widen visibility: private objects may only land in a private repository. */
export function canBridge(from: RepoRow, to: RepoRow): boolean {
  return from.visibility === "public" || to.visibility === "private";
}

/**
 * Base commits copied into the head repo become readable by everyone who can read the head repo, which
 * is not a subset of base readers (not even with the same owner: collaborators differ). Only public
 * bases may flow there; private bases use the git container, which never stores objects in the fork.
 */
export function canBridgeBase(baseRepo: RepoRow): boolean {
  return baseRepo.visibility === "public";
}

/** A pull request head must be the base itself or a live (not detached) direct fork of the base. */
export function isPullHeadRepo(
  baseRepo: Pick<RepoRow, "id">,
  headRepo: Pick<RepoRow, "id" | "fork_of_id" | "detached">,
): boolean {
  return headRepo.id === baseRepo.id || (headRepo.fork_of_id === baseRepo.id && headRepo.detached !== 1);
}

export type PullAccessSubject = { author_id: string };

/**
 * Whether a fork pull request may still move objects from its head into its base: the head must be a
 * fork of the base, the copy must not widen visibility, and the author must currently be able to read
 * both repositories. A reader who was removed from the base therefore stops receiving its history
 * (and stops feeding theirs) the moment access is revoked, not when someone notices.
 */
export async function pullHeadAccessible(
  pull: PullAccessSubject,
  baseRepo: RepoRow,
  headRepo: RepoRow,
): Promise<boolean> {
  if (baseRepo.id === headRepo.id) return true;