import { all, placeholders } from "../db"; import { repoDb } from "../scoped-db"; import { pullBaseRepoIds } from "../pull-scope"; /** Most bytes one account's fork pull requests may hold bridged into other repos at once. */ export const MAX_OPEN_BRIDGED_BYTES = 1024 * 1024 * 1024; export type BridgeCharge = { headOwnerId: string; baseRepoId: string; number: number }; /** * The bytes still pinned by bridges of pull requests whose head repo `ownerId` owns. Those pull requests live * in the parents of the owner's forks, so this asks each parent about the owner's forks of it. */ export async function openBridgedBytes(ownerId: string): Promise { const forks = await all<{ id: string; fork_of_id: string }>( "SELECT id, fork_of_id FROM repos WHERE owner_id = ? AND fork_of_id IS NOT NULL", ownerId, ); const byParent = new Map(); for (const fork of forks) byParent.set(fork.fork_of_id, [...(byParent.get(fork.fork_of_id) ?? []), fork.id]); const totals = await Promise.all( [...byParent].map(async ([parentId, forkIds]) => { const row = await repoDb(parentId).first<{ total: number | null }>( `SELECT SUM(bridged_bytes) AS total FROM pull_requests WHERE head_repo_id IN (${placeholders(forkIds.length)}) AND bridged_bytes > 0`, ...forkIds, ); return row?.total ?? 0; }), ); return totals.reduce((sum, total) => sum + total, 0); } /** Whether the head owner may start another relay; bridges reusing objects the target already has stay free. */ export async function bridgeQuotaAvailable(charge: BridgeCharge): Promise { return (await openBridgedBytes(charge.headOwnerId)) < MAX_OPEN_BRIDGED_BYTES; } export async function recordBridgedBytes(charge: BridgeCharge, bytes: number): Promise { if (bytes <= 0) return; await repoDb(charge.baseRepoId).run( `UPDATE pull_requests SET bridged_bytes = bridged_bytes + ? WHERE repo_id = ? AND issue_id = (SELECT id FROM issues WHERE repo_id = ? AND number = ?)`, bytes, charge.baseRepoId, charge.baseRepoId, charge.number, ); } /** Releases a pull request's charge once its hidden refs are gone. */ export async function releaseBridgedBytes(repoId: string, issueId: string): Promise { await repoDb(repoId).run( "UPDATE pull_requests SET bridged_bytes = 0 WHERE issue_id = ? AND bridged_bytes > 0", issueId, ); } /** Releases every charge for pull requests touching `repoId` after all of their hidden refs were dropped. */ export async function releaseBridgedBytesForRepo(repoId: string): Promise { const bases = await pullBaseRepoIds(repoId); await Promise.all([ repoDb(repoId).run("UPDATE pull_requests SET bridged_bytes = 0 WHERE bridged_bytes > 0"), ...bases .filter((id) => id !== repoId) .map((parentId) => repoDb(parentId).run( "UPDATE pull_requests SET bridged_bytes = 0 WHERE head_repo_id = ? AND bridged_bytes > 0", repoId, ), ), ]); }