CODE-070: subRevs rescans the root tree per commit — 35% of the git.git walk

Now
OPEN
Sev
MED
Rep
///bee
Ask
gritzko
Rev
///bee#8cb69a0
See
BEE-006

Input

Context

index/index.js:271:Wt subRevs costs 25.5 s of a 73.4 s commit walk over git.git (81726 commits) for ONE submodule: subTree misses once per commit (81656 times, 25.3 s) while the ODB reads themselves are 2.65 s — the JS cursor loop re-parses the root tree into a second capped cache. Memoizing subAt and pruning tree === pTree bought nothing (each commit's root tree is distinct). :1125 also takes submodulePaths from the TIP only, so a submodule moved or removed before the tip gets no dir revs, and every gap run re-walks the tip tree for the .gitmodules gate (221 tree parses for a 1-commit catch-up). Found in the 2026-09-18 index review.

Goals

Let git.getTreeDiff keep gitlinks (BEE-006:54:on is why it drops them) or add a leaf answering one tree's gitlink at a path; else readTree keeps gitlinks in its single parse. Gate the .gitmodules check on the tip's cached tree.

WIP

Design decisions

  1. subTree reads the gitlinks off the RAW tree bytes, not the git.tree cursor: that cursor mints a JS object and a 40-hex string PER ENTRY, while only a gitlink's mode starts "16", so two byte compares reject the other 558 entries of git.git's root tree. 3000 root trees: 1510 ms by cursor, 311 ms by scan, the ODB reads inside both some 300 ms.
  2. The tip's sub paths now come from .gitmodules (BEE-006:34:on allows it) with the TREE as the authority: a named path is kept only where subAt finds a real gitlink. A gap run reads one blob instead of parsing all 226 dirs of git.git's tip tree: 6.0 ms -> 1.1 ms per enumeration.
  3. The path list GROWS during the walk — every commit whose diff carries .gitmodules re-reads it — so a sub that moved or was dropped before the tip still gets its revs. One that never had a sub pays nothing: no .gitmodules in any diff, no list, no per-commit probe.

TODOs

Blockers and bummers

Outcome