grit://cooking/dogfood← what’s cooking
Thursday, October 29, 2026 · macOS 15.7 (Darwin 24.6.0)

Dogfood on scott-mbp: Large-monorepo engineer

7 repos · 39 commands · 0 broken · 9 slow · 4 filed

Large-monorepo persona on scott-mbp (macOS arm64, APFS): synthetic 300k and 1.02M-file repos, git/git with 82k extra branches, a 2.2 GiB pack straddling the 2^31 offset, case-collision checkouts, and a grit-lib program for status/cancel/concurrency. Grit matches or beats git on most daily-loop commands (status, diff, switch, rebase, stash, merge, clone, initial import, gc) and the four re-checked fixes hold. Real gaps: reset --hard / merge --abort over 30-60k-file diffs (#2311), 3.3x memory on a conflicted wide merge (#2312), status ignores Cancel inside the scan (#2313), merge refuses a detached HEAD (#2314).

os
macOS 15.7 (Darwin 24.6.0) · arm64
machine
scott-mbp (Apple M3, 24 GB RAM, APFS SSD, ~12-18 GB free disk)
versions
grit 0.5.0 (installed on PATH, not used for measurements) · main 440f8db · 2.52.0
repositories7
repofilescommits.gitnotes
scratch/300k-files300,0001498 MB1000 dirs x 300 small text files, plus wa/wb/wc/wnc/topic branches; .git 1208 MB while loose
scratch/1m-files1,020,0001368 MB3400 dirs x 300 files; 4105 MB while loose
git/git4,85782,616325 MBplus 82k synthetic branch refs in one copy
scratch/bigpack20,00312,400 MB3x800 MB random blobs + 20k small files, 2.23 GiB single pack
scratch/case-collide810 MBREADME/readme, Foo.c/foo.c, symlink, exec bit, aux.txt
scratch/untracked-ignored210 MB60k untracked + 60k ignored files
scratch/200k-wide-dir200,00010 MB200k entries in one directory
commandsgrit vs. system git, wall time
commandrepogritgitgrit/gitresultnotes
grit status (clean)scratch/300k-files340 ms330 ms1.0×okbimodal 340/600 ms on repeated runs; not filed
grit status (5 files touched)scratch/300k-files1.73 s1.22 s1.4×okfirst run after edits; repeat runs 600 ms
grit status (5k modified)scratch/300k-files670 ms990 ms0.7×ok
grit diff --stat (5k modified)scratch/300k-files1.04 s1.56 s0.7×ok
grit add (5k modified)scratch/300k-files1.81 s660 ms2.7×ok2.7x, 2x RSS; #1884 family
grit commit -m (5k staged)scratch/300k-files1.30 s490 ms2.7×ok2.65x, 2.2x RSS
grit commit -m (auto-stage 5k)scratch/300k-files1.18 s550 ms2.1×okgit commit -a
grit reset --hard (clean tree)scratch/300k-files880 ms860 ms1.0×ok#2268 fixed
grit reset --hard wnc (60k files differ)scratch/300k-files20.58 s7.34 s2.8×slow2.8x, 1.8x RSS; #2311
grit switch wb (60k files differ)scratch/300k-files3.70 s6.97 s0.5×okfaster than git
grit merge wc (35k paths, clean)scratch/300k-files1.02 s2.37 s0.4×oksame tree
grit merge wb (3000 conflicts, 33k paths)scratch/300k-files5.72 s6.16 s0.9×memory3.3x RSS; #2312
grit merge --check wbscratch/300k-files1.18 s——okno git equivalent; correct rc=1
grit merge --abort (after conflicted merge)scratch/300k-files7.63 s3.10 s2.5×slow2.5x, 2.2x RSS; #2311
grit reset --hard (after conflicted merge)scratch/300k-files12.29 s3.74 s3.3×slow3.3x; #2311
grit rebase wa (5 commits, upstream moved 30k files)scratch/300k-files2.41 s6.41 s0.4×okidentical tree hash
grit stash / stash pop (5k dirty)scratch/300k-files1.41 s3.13 s0.5×okpop 420 ms vs 1330 ms
grit commit (initial import 300k files)scratch/300k-files40.59 s170.00 s0.2×okgit add -A 161.6 s + commit 8.4 s; identical tree
grit commit (initial import 1.02M files)scratch/1m-files176.60 s——ok1.02M loose objects, then detached gc --auto ~14 min / ~1 GB RSS
grit status (1.02M files)scratch/1m-files3.08 s2.99 s1.0×okfirst run after a git-written index 6.0 s / 422 MB
grit commit 1 file (1.02M files)scratch/1m-files4.52 s2.96 s1.5×ok2x RSS; #1884 family
grit commit 1 file (300k, no TREE ext)scratch/300k-files1.13 s550 ms2.1×memory3x RSS; 2x time; #1884
grit status (300k, no TREE ext)scratch/300k-files650 ms560 ms1.2×memory2.1x RSS
grit clone (local file://)git/git10.11 s17.11 s0.6×oksame HEAD, fsck clean (one tag missingTaggerEntry warning, same as git)
grit log -- builtin/commit.cgit/git320 ms330 ms1.0×ok#1619 not reproduced on this build
grit blame Makefilegit/git1.09 s530 ms2.1×ok2x; ~1% lines attributed differently on ambiguous hunks
grit blame builtin/commit.cgit/git450 ms300 ms1.5×ok
grit branch (82k branches)git/git970 ms1.55 s0.6×okbranch -n 20 still 940 ms (reads all refs)
grit switch --detach v2.30.0 / v1.6.0git/git630 ms480 ms1.3×ok
grit gc (82k loose refs)git/git20.11 s70.59 s0.3×okpack-refs 9.3 s + reflog-expire 8.8 s of it; git pack-refs alone 21.8 s, reflog expire 36 s
grit cat main:z/f19999.txt (offset > 2^31)scratch/bigpack10 ms50 ms0.2×ok
grit fsck (2.2 GiB pack)scratch/bigpack2.18 s8.65 s0.3×memory2.0x RSS; #2287 family
grit clone file:// (2.2 GiB pack)scratch/bigpack45.40 s19.90 s2.3×slow2.3x time incl. checkout, 2x RSS; #2287
grit gc repack -f (2.2 GiB pack)scratch/bigpack3.89 s5.91 s0.7×ok2.0x RSS; git verify-pack/fsck accept output
grit cat HEAD:d (200k entries)scratch/200k-wide-dir70 ms60 ms1.2×ok#2269 fixed
grit clean --dry-runscratch/untracked-ignored500 ms130 ms3.8×slow3.8x, tiny absolute; Windows #2242 not testable
grit status --ignoredscratch/untracked-ignored100 ms50 ms2.0×ok
libprog: porcelain::status x3 + cancel + 10 handlesscratch/300k-files330 ms——ok10 concurrent handles: 1.75 s each, 610-715 MB total; cancel latency issue #2313
libprog: porcelain::status (1.02M files)scratch/1m-files3.08 s——okRSS creeps ~15-25 MB per repeated call on the same Repository handle
worked7
  • ›Clean-tree and 5k-file status/diff at 300k and 1.02M files are on par with or faster than git; --json and --markdown outputs parse and carry the same data
  • ›switch, merge (clean), rebase, stash/pop, gc and initial import over 300k files are 1.5-4x faster than git, with identical tree hashes and clean git fsck
  • ›Merge conflict state is byte-identical to git (same unmerged index entries and conflict file contents) and merge --check predicts correctly with exit code 1
  • ›Pack >2 GiB: lookups at offsets above 2^31 are fast and git verify-pack/fsck accept grit's repack output
  • ›Case-collision checkouts on APFS: grit warns on clone, keeps both tracked paths, and commits edits to the right path (git shows spurious modifications)
  • ›grit-lib public API (Repository::discover, porcelain::status, FlagCancel/ProgressSink) was enough to write the status/cancel/concurrency program without private modules; docs on StatusOptions and status() are clear
  • ›Automatic gc --auto after commit now exists and runs detached
problems13
  • ›reset --hard over 30-60k differing files 2.8x slower than git and 5x slower than grit's own switch; merge --abort 2.5x - filed #2311
  • ›Conflicted wide merge peaks at 3.3x git's RSS (384 vs 118 MB) - filed #2312
  • ›porcelain::status ignores Cancel/should_cancel inside the stat phase (cancel@1 s returns @2.9 s on 1M files) - filed #2313
  • ›grit merge refuses a detached HEAD (also --check) - filed #2314
  • ›grit fsck/clone/gc repack hold ~2x the pack size in RSS on a 2.2 GiB pack (4.7 GB vs git 2.36 GB mmap) - duplicate of #2287, not refiled
  • ›grit add/commit of 5k files and 1-file commits in 300k-1M repos 2-2.7x slower and 2-3x RSS vs git - duplicate of #1884, added data here
  • ›1.02M-file import leaves 1.02M loose objects (3.9 GiB on APFS) until a detached gc --auto runs ~14 min at ~1 GB RSS; no packing of bulk staged blobs - not filed (known direction #1883/#1859), mention for the speed agent
  • ›grit status on 300k files alternates between ~340 ms and ~600 ms on consecutive runs (sys time 1.5 s vs 3.0 s, 18k vs 5k involuntary context switches); git steady at 330 ms - cause unknown (thread scheduling on P/E cores?) so not filed
  • ›grit clean --dry-run 3.8x slower than git on 60k untracked + 60k ignored files (0.5 s vs 0.13 s) - too small in absolute terms, related to #2242
  • ›~1-2% of lines in blame on git/git hot files attribute a different commit than git blame on ambiguous diff hunks - judged diff-heuristic noise, not filed
  • ›diff3 conflict-marker base label is a 7-char abbrev (||||||| 4129611) where git prints 10 chars - cosmetic, not filed
  • ›Library status() RSS creeps by 15-25 MB per call when reusing one Repository on 1M files - minor, not filed
  • ›Not testable on this macOS machine: #2243 and #2242 (Windows NTFS); #2265 only verified on a synthetic pack because only 12 GB of disk were free
bugs filed4
#2311 #2312 #2313 #2314already reported: #1884 #2287 #1883 #1859 #1619 #2142
next time
  • ›Run the 2^32-object / >4 GiB pack boundary test (64-bit offset table writer) on a machine with >40 GB free disk, and the full linux clone with grit serve and RSS
  • ›Index written by git with split-index and sparse-index (cone) on the 300k repo: grit status/diff/commit speed and whether grit preserves or rewrites those extensions
  • ›Find the cause of grit status's alternating 340/600 ms runs on APFS (try --threads 4/8, taskpolicy, sample) and check if the slow mode disappears with write_index=false
  • ›Profile reset --hard vs switch on 60k files (#2311) after a fix, and the merge --abort path; re-measure RSS of conflicted merges at 30k/3000 and 60k/10k
  • ›Blobless/--blobs none clone and fetch on a 1M-file monorepo, and grit serve with 20 concurrent snapshot checkouts of a 1M-path repo (server RSS)
  • ›Bulk import: write one pack (not 1M loose objects) from grit commit/add for first imports, and time gc --auto at 1M loose objects against git gc --auto
  • ›Case-insensitive/NFD names created by Finder-style tools (decomposed filenames) in a checkout, plus long paths and many symlinks