56 lines · 3.0 KB
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { requireViewer, viewerMiddleware } from "./middleware";
import { pullInput, requirePullMutation } from "~/server/pull-access";
import { limitWrite } from "~/server/limits";
import { requireHeadPush } from "~/server/pull-merge";
import { MergeError } from "~/server/pulls";
import { kickBranch } from "~/server/merge-automation";
import { enableStackMerge, restackTree, stackParent, StackError } from "~/server/stacks";
import { badRequest, conflict } from "~/server/errors";

/** Replays a stacked pull request's own commits onto its parent's current head, cascading up the stack. */
export const restackPullRequest = createServerFn({ method: "POST" })
  .middleware([viewerMiddleware])
  .validator(pullInput.extend({ expectedHead: z.string().length(40) }))
  .handler(async ({ data, context }) => {
    const viewer = requireViewer(context.viewer);
    await limitWrite(viewer);
    const { pull, headRepo } = await requirePullMutation(data.owner, data.repo, data.number, viewer, "read");
    if (pull.state !== "open") throw badRequest("Pull request is not open");
    if (pull.head_sha !== data.expectedHead) throw conflict("The head branch changed; reload and try again");
    if (headRepo.id !== pull.repo_id) throw badRequest("Only same-repository branches can be restacked here");
    await requireHeadPush(headRepo, pull.head_ref, viewer);
    const parent = await stackParent(pull);
    if (!parent) throw badRequest("This pull request is not stacked on another one");
    const outcome = await restackTree(pull, parent.head_sha, [pull.restack_upstream_sha, pull.base_sha], {
      actorId: viewer.id,
      committer: { name: viewer.displayName ?? viewer.name, email: viewer.email },
      parentNumber: parent.number,
      ontoRef: parent.head_ref,
    });
    if (outcome === "conflict") throw conflict("The commits conflict with the parent; restack locally and force-push");
    if (outcome === "unsupported") throw conflict("This history can't be restacked on the server; restack locally");
    if (outcome === "moved") throw conflict("The head branch moved; reload and try again");
    return { outcome };
  });

/** Merges every pull request from the bottom of the stack up to this one, each as its checks pass. */
export const mergeStack = createServerFn({ method: "POST" })
  .middleware([viewerMiddleware])