import { badRequest, forbidden } from "../errors";
import type { GitStore } from "../git/store";
import { MAX_POINTER_BYTES, parseLfsPointer } from "../lfs/pointer";
import { readBounded } from "./blob-read";
import { attributeGlob } from "~/lib/gitattributes";
const MAX_ATTRIBUTES_BYTES = 64 * 1024;
const MAX_ATTRIBUTE_RULES = 500;
const decoder = new TextDecoder();
type Rule = { regex: RegExp; anchored: boolean; lfs: boolean };
/** Root `.gitattributes` rules that set or unset `filter=lfs`; later rules win, as in git. */
async function lfsRules(store: GitStore, rootTree: string): Promise<Rule[]> {
const entry = await store.entryAtPath(rootTree, ".gitattributes");
if (!entry || entry.type === "tree" || entry.type === "gitlink") return [];
const bytes = (await readBounded(store, entry.sha, MAX_ATTRIBUTES_BYTES))?.bytes;
if (!bytes) return [];
const rules: Rule[] = [];
for (const line of decoder.decode(bytes).split("\n").slice(0, MAX_ATTRIBUTE_RULES)) {
const [pattern, ...attributes] = line.trim().split(/\s+/);
if (!pattern || pattern.startsWith("#")) continue;
const lfs = attributes.includes("filter=lfs");
if (!lfs && !attributes.some((attribute) => attribute === "-filter" || attribute === "!filter")) continue;
const anchored = pattern.includes("/");
rules.push({ regex: attributeGlob(pattern.replace(/^\//, "")), anchored, lfs });
}
return rules;
}
function isLfsTracked(rules: Rule[], path: string): boolean {
let tracked = false;
const basename = path.slice(path.lastIndexOf("/") + 1);
for (const rule of rules) if (rule.regex.test(rule.anchored ? path : basename)) tracked = rule.lfs;
return tracked;
}
function assertDistinct(paths: string[]): void {
const seen = new Set<string>();
for (const path of paths) {