248 lines · 9.5 KB
import { z } from "zod";
import { badRequest, conflict, HttpError, notFoundError } from "../../errors";
import { forkRepo } from "../../api/repos";
import { announcePush, assertDirectUpdate, rethrowPush } from "../../git/web-commit";
import { branchSha, listRefs } from "../../git/refs";
import { signatureNow } from "../../git/objects";
import { withStore, ZERO_SHA } from "../../git/store";
import { commitChanges, createBranch, isValidRefName } from "../../git/writer";
import type { FileChangeInput } from "../../git/writer";
import { limitWrite } from "../../limits";
import type { McpCaller } from "../protocol";
import { repoHub } from "../hub-do";
import { normalizeClaimPath, overlaps } from "../coordination";
import type { Lease } from "../coordination";
import { apiCtx, apiJson, defineTool, repoArgs, scopedRepo, withTrailers } from "./base";
import { resolveRef } from "./content";
import { holderKey } from "./coordination";

const MAX_FILES = 100;
const MAX_TOTAL_BYTES = 8 * 1024 * 1024;
const MAX_SLUG = 40;
const WORKSPACE_CLAIM_MS = 2 * 60 * 60 * 1000;

const branchName = z.string().min(1).max(200);

function slugify(value: string): string {
  return (
    value
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "-")
      .replace(/^-+|-+$/g, "")
      .slice(0, MAX_SLUG) || "task"
  );
}

async function createBranchAt(caller: McpCaller, ownerName: string, repoName: string, name: string, from?: string) {
  if (!isValidRefName(name)) throw badRequest("Invalid branch name");
  await limitWrite(caller.viewer);
  const { repo, permission } = await scopedRepo(caller, ownerName, repoName, "contents", "write");
  const refs = await listRefs(repo);