274 lines · 10.9 KB
import { structuredPatch } from "diff";
import { env, waitUntil } from "cloudflare:workers";
import type { GitStore } from "./store";
import type { DiffResult, FileChange } from "~/lib/types";

const MAX_CONTENT_FILES = 300;
const MAX_TREE_READS = 2000;
const MAX_CHANGED_FILES = 5000;
const MAX_EDIT_LENGTH = 20_000;
const DIFF_TIMEOUT_MS = 1500;
const MAX_BLOB_BYTES = 512 * 1024;
const BINARY_SNIFF_BYTES = 8000;
const BLOB_CONCURRENCY = 8;
const TREE_CONCURRENCY = 8;
const QUOTED_PATH_CHARS = /["\\\x00-\x1f\x7f]/;
const C_ESCAPES: Record<string, string> = { "\n": "\\n", "\t": "\\t", '"': '\\"', "\\": "\\\\" };

const CONTEXT_LINES = 3;
const MAX_CACHED_PATCH_BYTES = 4 * 1024 * 1024;
const DIFF_CACHE_TTL_SECONDS = 60 * 60 * 24 * 7;
const DIFF_CACHE_VERSION = "v2";
const decoder = new TextDecoder();

/** Quotes a path for patch text the way git does, so names with newlines cannot forge header or hunk lines. */
function patchPath(prefix: string, path: string): string {
  const full = `${prefix}${path}`;
  if (!QUOTED_PATH_CHARS.test(full)) return full;
  const escaped = full.replace(/["\\\x00-\x1f\x7f]/g, (char) => {
    return C_ESCAPES[char] ?? `\\${char.charCodeAt(0).toString(8).padStart(3, "0")}`;
  });
  return `"${escaped}"`;
}

type Entry = { name: string; mode: string; hash: string; type: ArtifactsTreeEntryType };

function joinPath(parent: string, name: string): string {
  return parent ? `${parent}/${name}` : name;
}

async function entriesOf(store: GitStore, sha: string | null): Promise<Map<string, Entry>> {