69 lines · 2.9 KB
import { listRefs, resolveRevision } from "../../git/refs";
import type { ResolvedRevision } from "../../git/refs";
import { assertVisibleRevision } from "../../git/reachable";
import { withStore } from "../../git/store";
import { notFoundError } from "../../errors";
import type { RepoRow, TreeEntry } from "~/lib/types";

export const MAX_FILE_BYTES = 1024 * 1024;
const MAX_TREE_ENTRIES = 1000;
const decoder = new TextDecoder("utf-8", { fatal: true });

/** Resolves a branch, tag or reachable commit (default branch when omitted). Hidden commits stay hidden. */
export async function resolveRef(repo: RepoRow, ref: string | undefined): Promise<ResolvedRevision> {
  const refs = await listRefs(repo);
  const revision = resolveRevision(refs, ref || repo.default_branch);
  if (!revision) throw notFoundError(`Unknown ref ${ref ?? repo.default_branch}`);
  await assertVisibleRevision(repo, revision);
  return revision;
}

export async function listTreeAt(repo: RepoRow, revision: ResolvedRevision, path: string) {
  return withStore(repo.artifacts_name, async (store) => {
    const commit = await store.commit(revision.sha);
    const entry = commit ? await store.entryAtPath(commit.treeSha, path) : null;
    if (!entry || entry.type !== "tree") throw notFoundError("Directory not found");
    const entries: TreeEntry[] = await store.listTree(entry.sha, entry.path);
    return { entries: entries.slice(0, MAX_TREE_ENTRIES), truncated: entries.length > MAX_TREE_ENTRIES };
  });
}

export type FileRead = {
  path: string;
  sha: string;
  size: number;
  binary: boolean;
  text: string | null;
  base64: string | null;
};

/** Reads one blob; text when it decodes as UTF-8, base64 otherwise, and never more than MAX_FILE_BYTES. */