100 lines · 3.8 KB
import { badRequest, conflict, forbidden, notFoundError } from "../errors";
import { triggerPush } from "../events/push-trigger";
import { directUpdateViolation, loadProtections, protectionFor } from "../protection";
import { signatureNow } from "./objects";
import { branchSha, listRefs } from "./refs";
import { withStore, ZERO_SHA } from "./store";
import { commitChanges, isValidRefName, PushRejectedError } from "./writer";
import type { FileChangeInput } from "./writer";
import { repoReadOnlyReason } from "../repos";
import type { Permission, RepoRow, Viewer } from "~/lib/types";

export async function assertDirectUpdate(repo: RepoRow, permission: Permission, branch: string, deletion: boolean) {
  const readOnly = repoReadOnlyReason(repo);
  if (readOnly) throw forbidden(readOnly);
  const violation = directUpdateViolation(protectionFor(await loadProtections(repo.id), branch), permission, deletion);
  if (violation) throw forbidden(violation);
}

export async function announcePush(repo: RepoRow, viewer: Viewer, ref: string, before: string, after: string) {
  await triggerPush({
    source: "internal",
    repoId: repo.id,
    artifactsName: repo.artifacts_name,
    ref,
    before,
    after,
    pusherId: viewer.id,
  });
}

export function rethrowPush(error: unknown): never {
  if (error instanceof PushRejectedError) throw conflict(error.message);
  throw error;
}

/**
 * Commits a single-file create/edit/rename/delete as the web editor does, optionally onto a new branch.
 * Path safety (GIT-7) is enforced by commitChanges; rejections surface as 409 "Invalid path: ...".
 */
export async function commitFileChange(input: {