website

#astro#js#html#css

git clone https://git.pyrossh.dev/website

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


src/_data/repoCommits.js
3c79a68 1
import { html as renderDiffHtml, parse as parseDiff } from "diff2html";
ada6e53 2
import { readCache, writeCache } from "../_helpers/dataCache.js";
ada6e53 3
import { discoverLocalRepos, getCommitDiff, getHeadSha } from "../_helpers/gitRepos.js";
ada6e53 4
import { mapPool } from "../_helpers/pool.js";
ada6e53 5
ada6e53 6
const CONCURRENCY = 8;
3c79a68 7
778e0a4 8
// Some repos have vendored/generated/binary-as-text blobs committed at some
778e0a4 9
// point in their history (a minified bundle, a compiled binary git doesn't
778e0a4 10
// detect as binary, ...) — `git show -p` dumps the whole file as a diff on
778e0a4 11
// every commit that touches it, and diff2html's per-line markup multiplies
778e0a4 12
// that further. Left unguarded, that's easily 100s of MB of HTML per repo,
778e0a4 13
// held for every commit at once across every repo — well past Node's default
778e0a4 14
// heap. Cap it the way GitHub's own diff viewer does: skip rendering past a
778e0a4 15
// size threshold rather than trying to syntax-highlight a multi-MB blob.
778e0a4 16
const MAX_DIFF_SIZE = 300 * 1024;
778e0a4 17
const DIFF_TOO_LARGE_HTML = '<p class="diff-too-large">This diff is too large to display.</p>';
778e0a4 18
3c79a68 19
const toDiffHtml = (diffText) => {
3c79a68 20
  const idx = diffText.indexOf("diff --git");
3c79a68 21
  const patch = idx >= 0 ? diffText.slice(idx) : "";
3c79a68 22
  if (!patch.trim()) return "";
778e0a4 23
  if (patch.length > MAX_DIFF_SIZE) return DIFF_TOO_LARGE_HTML;
3c79a68 24
  try {
3c79a68 25
    const json = parseDiff(patch);
3c79a68 26
    return renderDiffHtml(json, { drawFileList: true, matching: "lines" });
3c79a68 27
  } catch {
3c79a68 28
    return "";
3c79a68 29
  }
3c79a68 30
};
3c79a68 31
3c79a68 32
export default async () => {
3c79a68 33
  const repos = discoverLocalRepos();
3c79a68 34
  const pages = [];
3c79a68 35
  for (const repo of repos) {
ada6e53 36
    // Unlike repoFiles.js, none of this reads the working tree — `git
ada6e53 37
    // show` only ever sees committed history — so it's safe to cache on
ada6e53 38
    // HEAD sha alone, with no working-tree-clean requirement. That
ada6e53 39
    // matters because this repo (website) is usually dirty while it's
ada6e53 40
    // being actively worked on, and its commit history is the most
ada6e53 41
    // expensive part of the build.
ada6e53 42
    const sha = getHeadSha(repo.dir);
ada6e53 43
    const cacheKey = `commits-${repo.id}`;
ada6e53 44
ada6e53 45
    let entries = sha ? readCache(cacheKey, sha) : null;
ada6e53 46
    if (!entries) {
ada6e53 47
      entries = await mapPool(repo.commits, CONCURRENCY, async (commit) => {
ada6e53 48
        const diffText = await getCommitDiff(repo.dir, commit.hash);
ada6e53 49
        return { commit, diffHtml: toDiffHtml(diffText) };
ada6e53 50
      });
ada6e53 51
      if (sha) writeCache(cacheKey, sha, entries);
3c79a68 52
    }
ada6e53 53
09c6a56 54
    // Only `id`/`data`/`version` ever get read off `item.repo` in
09c6a56 55
    // commit.njk — not the full `repo` object's `files`/`tree`/`commits`/
09c6a56 56
    // `readme`. Eleventy's paginator deep-clones each page's data slice for
09c6a56 57
    // isolation, so embedding the whole repo (every commit's metadata,
09c6a56 58
    // every file, the tree, the README) here means that whole structure
09c6a56 59
    // gets cloned once per commit page — the actual multiplier behind the
09c6a56 60
    // OOM, not just the diff HTML size.
09c6a56 61
    const slimRepo = { id: repo.id, data: repo.data, version: repo.version };
778e0a4 62
    for (const entry of entries) pages.push({ repoId: repo.id, repo: slimRepo, ...entry });
3c79a68 63
  }
3c79a68 64
  return pages;
3c79a68 65
};