grit://cooking/changelog← what’s cooking
Monday, October 26, 2026 · release v0.5.21

86 changes landed on main

75 issue fixes · 11 other changes

v0.5.21 mostly cuts memory and time in clone, fetch, gc/repack, fsck and merge on large repos, and fixes many concurrency, Windows and Git-compatibility bugs.

  • ›Streaming upload-pack, repack, gc, bundle and fsck: cargo clone/gc/bundle drop from minutes and GBs to seconds
  • ›Global -C, -c, --git-dir and clone -q; GIT_ASKPASS and credential helper fixes for HTTPS
  • ›Parallel add, commit, clone and fetch no longer lose index updates or fail on races
  • ›Windows: faster switch, reset --hard and first status; hook paths, filters and symlink mode fixed
  • ›Merge accepts tags and revisions, detects rename/rename conflicts, and is no longer quadratic
  • ›Fixes for pack/idx checksums, midx v1 default, shallow clone gc/fsck, and sparse-checkout
$ git log 77fd8a48..v0.5.21304 commits on GitHub ↗300+ files +13,281 −2,032
Highlightsmeasured by the reviewers, before → after
upload-pack: --depth 1 on a repo with annotated tags sends full history; --no-single-branch fails
Depth-1 commits sent on 10k-commit tagged fixture9001× better████████████████████████ 9,001 commits █ 1 commits
grit add of untracked files is quadratic in directory count: 4000 dirs = 93 s, 1 GB RSS (git add -A: 0.26 s)
2,000 flat directories: grit/git time ratio469× better████████████████████████ 622.3 x █ 1.3 x
grit commit on split-index repo with index.skipHash/manyFiles leaves index unreadable by git
100k-file forced split-index rebuild149× better████████████████████████ 18.0 seconds median of 5 █ 0.121 seconds median of 5
upload-pack thin-pack incremental fetch (1 new commit) on cargo: 26 s / 1.6 GB RSS (git: 25 ms / 11 MB)
Cargo one-commit thin-pack wall time (median of 3)138× better████████████████████████ 29.6 seconds █ 0.213 seconds
upload-pack ls-refs peels every ref before ref-prefix filtering: 1.8 s per request on 120k refs (git 0.00 s)
120,001 packed refs: ls-refs one-tag prefix median124× better████████████████████████ 529.8 ms █ 4.3 ms
issues fixed75
  • ›Windows: switch/reset --hard that delete ~2000 files take 3-10 s (once 281 s) vs git 0.3-0.6 s, erratic#17549504e943

    Delete-heavy switch and hard reset now avoid redundant per-file metadata/content work. Hard reset delegates tree-diff deletions to wholesale directory removal, scans unchanged entries once, verifies only dirty/racy candidates in parallel, and preserves index stats from path-based records; switch reuses batched scan stats and hashes racy files in parallel.

    reset/hard-delete-heavy grit median wall: 480.2 ms → 236.5 ms (2.0× better)reset/hard-delete-heavy grit/system-git ratio: 2.1 ratio → 1.0 ratio (2.0× better)reset/hard-delete-heavy grit peak RSS: 64.7 MB → 51.2 MB (26% better)switch/delete-heavy grit median wall: 125.1 ms → 121.3 ms (3% better)
  • ›upload-pack full clone: cargo 91s/3.6GB RSS (git 2.4s/165MB); git/git exceeds 11.7GB after 500s (git 20s/530MB)#175351496d8f

    This change turns upload-pack for already-packed repositories into a bounded streaming pipeline. It lazily reuses packed whole objects and deltas, frames bytes directly onto side-band output, and makes object enumeration and offset lookup cheap enough for large full clones.

    Cargo full-clone server wall time: 97.5 seconds → 3.2 seconds (30× better)Cargo full-clone server peak RSS: 3,676,728 KiB → 302,732 KiB (12× better)Cargo full-clone wall time versus system git: 3.2 seconds (git reference → branch) → 3.2 seconds (git reference → branch) (1% better)Cargo full-clone peak RSS versus system git: 165,828 KiB (git reference → branch) → 302,732 KiB (git reference → branch) (45% worse)
  • ›No global -C <dir>, -c key=value or --git-dir (and no -q on clone): CI scripts cannot target a repo or override config#17568e9e4e09

    Adds invocation-scoped repository targeting and config overrides to `grit`, plus consistent quiet handling for clone, fetch, pull, and push. The implementation is library-first: typed config overrides and repository-path builders live in `grit-lib`, while the CLI parses and applies them before dispatch.

  • ›Odb::exists returns bool and turns custom-store I/O errors into 'object missing' (63 call sites incl. fetch/push)#175117c00f9f

    Adds a fallible object-existence API and migrates correctness-sensitive callers so custom-store outages remain typed errors instead of being misclassified as missing objects. The round-2 revision makes lookup ordering strict and consistent with object reads, while preserving the old lenient advisory API.

    Packed-object `Odb::exists` hit, pinned median of 7 alternating runs: 862.8 ns/probe → 870.3 ns/probe (1% worse)Missing-object `Odb::exists`, pinned median of 7 alternating runs: 5,433 ns/probe → 5,351 ns/probe (2% better)
  • ›Pluggable backends: no shared helpers or conformance suite for RefStore; Repository still needs a .git skeleton#17393c2c9795

    This makes custom reference backends practical by exposing shared transaction semantics and a reusable conformance suite, while aligning memory and reftable behavior. It also lets repositories open with injected object/ref stores without a fake .git skeleton and fixes relative ODB paths. The round-3 fixes specifically make linked-worktree and log-only symref reflogs correct.

  • ›grit gc/repack on an already-packed cargo clone: 72s and 4.3 GB RSS (git gc: 1.1s, 203 MB)#168410f19cd5

    Adds gc auto-skip behavior and a pack-reuse rewrite path intended to avoid rebuilding deltas for already-packed repositories. It also repairs trusted index-row generation, handles excluded delta bases safely, disk-stages streamed packs, and keeps explicit repack from auto-skipping.

    Cargo explicit repack wall time (origin/main → branch): 91.3 seconds → 92.5 seconds (1% worse)Cargo explicit repack peak RSS (origin/main → branch): 2,088,376 KiB → 1,958,632 KiB (7% better)Cargo repack wall time (system Git → branch): 1.2 seconds → 92.5 seconds (99% worse)Cargo repack peak RSS (system Git → branch): 168,992 KiB → 1,958,632 KiB (91% worse)
  • ›Windows: clean/smudge filters skipped when sh not on PATH; grit add stores LFS files raw despite required=true#18101c01643f

    Required clean/smudge filters can no longer silently store or compare raw bytes when their command fails. Shell commands now use Git-aware interpreter resolution, including Git for Windows' bundled shell when only the cmd directory is on PATH, while optional filter failures retain Git's raw-content fallback.

    300-file required-filter add latency: main to branch: 78.2 ms median of 5 measured runs → 79.1 ms median of 5 measured runs (1% worse)300-file required-filter add latency: system Git to branch: 308.9 ms median of 5 measured runs → 79.1 ms median of 5 measured runs (3.9× better)300-file required-filter add peak RSS: main to branch: 12,804 KiB, one measured run → 13,324 KiB, one measured run (4% worse)
  • ›HTTPS clone with `credential.helper store` fails: grit sends url=<full request URL>, git-credential-store never matches#179337c2ba0a

    HTTP authentication now asks credential helpers with the repository-level protocol and host, adding the repository path only when credential.useHttpPath applies. This lets host-scoped credential-store/keychain entries authenticate clone, fetch, and push while retaining URL-scoped credential configuration matching.

  • ›Concurrent `grit commit -m MSG -- <path>` races on COMMIT_EDITMSG: wrong message committed, bogus 'empty commit message'#1791cb27c72a

    Path-scoped commits now take index.lock before reading repository state and retain it until the branch and real index are safely updated. Commit messages remain in memory unless a commit-msg hook actually runs, and the commit uses one HEAD snapshot for both its parent/tree basis and the final compare-and-swap.

    20k-file path commit median, origin/main vs branch: 106.6 ms → 113.9 ms (6% worse)20k-file path commit median peak RSS, origin/main vs branch: 27,896 KiB → 31,340 KiB (11% worse)
  • ›grit bundle create --all on rust-lang/cargo: 197 s and 5.0 GB peak (git bundle create: 1.6 s, 114 MB)#17589ac763c0

    Bundle creation now streams a pack while reusing existing SHA-1 pack entries and deltas, avoiding inflated-object retention and a second full output buffer. The follow-up correctly keeps SHA-256 and loose-only repositories on windowed delta search, and bundle header selection now preserves annotated tag objects across commit prerequisites.

    bundle create --all: origin/main time-to-OOM vs review completion: 91.6 seconds → 4.2 seconds (22× better)bundle create --all: origin/main vs review peak RSS: 15,027,480 KiB → 464,692 KiB (32× better)bundle create --all: system Git vs review wall time: 1.7 seconds → 4.2 seconds (59% worse)bundle create --all: system Git vs review peak RSS: 311,948 KiB → 464,692 KiB (33% worse)
  • ›grit-lib: no cancellation for clone/fetch/checkout/status, CLI-only uncommitted-diff logic, models not serializable#17042f4b7d09

    Adds a public cancellation model and threads it through fetch, bundle ingestion, status, push, and related call sites, with typed cancellation errors and focused regressions. Round 5 attempts to extend cancellation into parallel pack indexing, but live progress cancellation and post-index ingest cancellation remain ineffective.

  • ›grit commit -- <paths> with index.lock held exits 1 but still creates the commit; index left stale (MM)#17892f4b7d09

    Partial commits now reserve the repository index before doing visible work, then write the final index through that same lock after the branch update. Held locks produce a typed library error and stable CLI JSON for path-, hunk-, and patch-file-scoped commits, so retries are safe and no stale MM index state is left behind.

    patch-file commit time on 50,500 files: current origin/main to branch: 41.8 ms (30 runs, mean) → 37.8 ms (30 runs, mean) (11% better)path commit time on 50,500 files: current origin/main to branch: 74.9 ms (30 runs, mean) → 78.6 ms (30 runs, mean) (5% worse)patch-file peak RSS: current origin/main to branch: 27,208 KiB → 27,352 KiB (1% worse)path commit peak RSS: current origin/main to branch: 35,328 KiB → 34,164 KiB (3% better)
  • ›grit revs A..B walks the entire history: 800ms/126MB on git/git for a 20-commit range (git rev-list: 2ms/12MB)#16692657344c

    Unlimited default-order ranges now use the hide-frontier traversal even without a limit, avoiding full-history include/exclude closure materialization. Follow-up fixes preserve Git-compatible handling of unreadable history behind exclusions and honor commit-graph opt-out, with cursor compatibility and system-Git regressions retained.

    git/git HEAD~20..HEAD listing median latency: 829.4 ms → 10.3 ms (81× better)git/git HEAD~20..HEAD listing median peak RSS: 125,144 KiB → 25,756 KiB (4.9× better)
  • ›Repository::paths_match_pathspecs silently returns false for :(attr:...) pathspecs#1796d96ea560

    Repository pathspec matching now evaluates `:(attr:...)` against the repository's real attribute stack, including user-defined and builtin macros and case-insensitive patterns when configured. It also validates malformed attr clauses up front and exposes reusable matching through `PathspecMatcher`, so diff filtering loads attributes once per pass.

    20k-file plain diff median latency: 33.1 ms → 32.6 ms (2% better)20k-file attr diff median latency (origin/main is incorrect): 20.5 ms → 52.1 ms (61% worse)20k-file plain diff peak RSS: 35.0 MiB → 34.8 MiB (1% better)
  • ›fetch with default tag following spends 2.5 s (cargo) to 19 s (git/git) silently in retain_following_tags#1794489f267b

    Fetch tag following now classifies delivered tag targets through pack membership instead of walking and inflating the fetched object graph. Unadvertised annotated tags are peeled only through locally stored tag objects, preventing partial-clone promisor fetches while preserving Git-compatible followed-tag sets.

    Cargo default-tag fetch median (3 localhost git:// runs): 6.2 seconds → 5.1 seconds (22% better)Feature fetch versus system Git median: 5.2 seconds → 5.1 seconds (2% better)
  • ›Hook stdout/stderr is written to the process stderr by grit-lib and HookError carries no output#17955d61d87b

    Hook execution no longer loses output produced by earlier hooks when a later hook cannot spawn. The accumulated bytes travel in HookError::Spawn and are exposed consistently to embedders and CLI human/Markdown rendering, with typed tests covering the complete failure path.

  • ›grit clone checkout of a 200k-file repo peaks at 537 MB RSS and is 1.5x slower than git (git: 57 MB, 3.8s)#1678b7e20e46

    Populated-tree checkout chunks now mutate only the in-memory index. After the final chunk, grit builds the TREE cache extension once and writes the index once, avoiding a full commit-tree walk and discarded cache tree for every 2,048-file chunk.

    synth-100k grit clone median: 4,359 ms → 3,270 ms (33% better)synth-100k grit/git wall-time ratio: 1.3 ratio → 1.0 ratio (31% better)synth-100k grit peak RSS: 157,761,536 bytes → 157,003,776 bytes (0% better)
  • ›Parallel `grit add <file>` processes silently lose index updates: all exit 0, only a few files end up staged#179202082248

    Index-mutating add operations now acquire index.lock before reading the index and write the updated index through that same lock. Concurrent writers can no longer report success and then overwrite one another; related rm and tracked-staging paths also preserve concurrent updates.

    successful parallel adds present in index (30 successful processes): 1 paths → 30 paths (30× better)final index entries after 30 successful adds plus base: 2 entries → 31 entries (16× better)single modified-file add median on 20k-file repo: 14.3 ms → 14.4 ms (1% worse)
  • ›docs/docs/grit-lib.md API map lists 443 of 615 paths that are private or missing (grit_lib::serve, odb, transfer, ...)#1822d1ffc337

    Adds a docs CI guard that treats every written `grit_lib::…` path and hand-written docs.rs grit-lib URL as a public-API claim, resolving it against local rustdoc. It also rewrites stale library-guide paths to their canonical public locations and documents the public no-op progress sink.

  • ›Windows (core.symlinks=false): grit commit turns every untouched symlink (120000) into a regular file (100644)#18133b629c90

    Introduces one library policy for translating worktree modes into index modes, then uses it throughout staging and worktree comparison. A symlink represented by a regular file on a core.symlinks=false checkout now remains mode 120000, stays clean when untouched, and records edited target text as a symlink blob.

    Warm status median, 50 interleaved runs: 8.9 ms → 8.2 ms (9% better)Warm diff median, 50 interleaved runs: 7.4 ms → 5.8 ms (27% better)
  • ›reset --hard, switch and merge ignore cone sparse-checkout: out-of-cone files are materialized, skip-worktree cleared#1814157a05dd

    Sparse-aware checkout updates now keep paths outside the configured cone out of the worktree and preserve their index representation across reset, switch, merge, restore, pull/rebase, and sparse-index collapse. The round-2 amendment additionally allows switch and merge to proceed when an exact skipped added path already has an untracked file, leaving that file untouched and reporting it as a tracked modification like Git.

  • ›grit ignores a git-started rebase: switch works mid-rebase, then git --continue moves main; grit --abort says no rebase#18150ae050c8

    Grit now recognizes Git-started rebases across status, switch, and rebase session handling. It prevents branch switching while an operation is paused, reports the rebased branch correctly, safely aborts a foreign rebase including rerere cleanup, and writes enough Git-compatible paused state for Git to continue a grit-started rebase.

  • ›upload-pack partial clone (--filter=blob:none) of cargo: 43 s/1.9 GB (git 0.6 s/155 MB) and fails 'bad object' (tag)#1819d52d28e4

    Filtered upload-pack now preserves every annotated tag in a peel chain and applies partial-clone filters directly during pack object enumeration. This removes the expensive rev-list/reopen/hex round trip, fixes filtered fetches with haves, and avoids loading peer blobs when a blobless pack cannot use them.

    Blobless clone median on local 4,001-commit no-bitmap fixture: 2.8 seconds → 0.219 seconds (13× better)Blobless clone median peak RSS on local fixture: 153,432 KiB → 34,976 KiB (4.4× better)
  • ›upload-pack ls-refs peels every ref before ref-prefix filtering: 1.8 s per request on 120k refs (git 0.00 s)#182006a3ecfc

    Moves protocol-v2 ls-refs prefix selection ahead of ref enumeration and object peeling, so a narrow request scans only matching refs. It also reuses packed-ref peel records, avoids object reads when peel was not requested, and adds compatibility/performance coverage for both v2 and v0 advertisements.

    120,001 packed refs: ls-refs one-tag prefix median: 529.8 ms → 4.3 ms (124× better)120,001 packed refs: v0 advertisement median: 574.7 ms → 152.2 ms (3.8× better)
  • ›grit merge accepts only branch names: tags, commit ids, ORIG_HEAD and rev expressions fail with 'no branch named'#1817ecf4ab43

    Merge now resolves its source through a documented grit-lib API instead of a CLI-only branch lookup. It accepts local and remote branches, lightweight and annotated tags, full or abbreviated object IDs, pseudo-refs such as ORIG_HEAD, and expressions such as tag~1, while retaining structured errors for scripts.

  • ›grit-lib API: sentinel branch strings differ per outcome, duplicate stash APIs, porcelain::rebase has no rebase#1798b83c306e

    This replaces sentinel branch strings and duplicated stash surfaces with typed outcomes, moves live history message helpers out of the misleading porcelain::rebase module, and gives porcelain mutations typed Signature identities resolved through Repository. The round-two amendment closes the corruption hole by validating rewrite committers before any object write and tightens Signature parsing.

    history reword vs scripted git rebase: grit median: 37.5 ms → 11.4 ms (3.3× better)history reword --update-refs=head: grit median: 14.3 ms → 11.0 ms (30% better)history fixup: grit median: 43.9 ms → 30.9 ms (42% better)history squash: grit median: 10.5 ms → 6.2 ms (70% better)
  • ›Unreadable/unreachable remote exits 1, not documented 128 (remote_unreadable), for clone, fetch and push#175582e00029

    Introduces a typed remote-unreadable error from the library through the CLI, giving clone, fetch, pull, push, and remote refs stable JSON classification and exit 128. It also tightens local repository validation and makes linked-worktree and separate-git-dir checkouts usable as local remotes.

    no-op fetch, 1541 refs, median: 82.8 ms → 83.6 ms (1% worse)up-to-date push, 1541 refs, median: 12.1 ms → 12.1 ms (1% worse)
  • ›grit clone over https peaks at 1.7GB RSS on git/git and 475MB on cargo (git: ~98MB / ~45MB)#16725d513685

    Received packs are still spooled to disk, but indexing now bounds mmap residency, reads random delta data with positioned I/O, streams index output, and completes thin packs in place. This removes pack-size-scaled RSS while also correcting thin-pack pack/index checksum naming.

    Cargo pack ingest elapsed, origin/main to branch: 2.2 seconds, median of 3 → 1.3 seconds, median of 3 (78% better)Cargo pack ingest peak RSS, origin/main to branch: 116,608 KiB, median of 3 → 33,412 KiB, median of 3 (3.5× better)Cargo pack ingest elapsed, system git to branch: 3.0 seconds, median of 3 → 1.3 seconds, median of 3 (2.4× better)Cargo pack ingest peak RSS, system git to branch: 31,532 KiB, median of 3 → 33,412 KiB, median of 3 (6% worse)
  • ›upload-pack v2 fetch: deepen-not <ref> rejected as invalid oid; want-ref served without the wanted-refs section#18214b928ba1

    Upload-pack now resolves deepen-not ref names and annotated tags, emits the required wanted-refs section for v2 ref-in-want fetches, and advertises only enabled protocol-v2 features. The round-2 amendments reject duplicate want-ref lines and unconfigured sideband-all requests before any packfile is sent.

    Full clone median, origin/main to branch: 10.1 seconds → 10.2 seconds (1% worse)Shallow-exclude median, system Git to branch grit: 0.738 seconds → 10.2 seconds (93% worse)
  • ›grit fetch of a small update writes a .pack whose .idx has another pack checksum; git fsck fails, HEAD unreadable#1844515b23f1

    Pack installation now waits until thin-pack completion before choosing the pack checksum, index trailer, and final filename. Regression coverage exercises both network fetch and receive-pack, proving repositories remain readable by stock Git.

    One-line thin fetch median: 53.0 ms (10 runs) → 52.8 ms (10 runs) (0% better)Fetch latency versus system Git: 0.553 ratio; system Git median 95.8101 ms → 0.551 ratio; system Git median 95.8101 ms (0% better)
  • ›grit rm --cached refuses a file edited since commit, and its error tells you to use --cached#185166b5719d

    This makes index-only removal preserve edited working files when the index still matches HEAD, while continuing to reject the one unsafe case where staged content differs from both HEAD and disk. It also replaces a string-only refusal with per-path typed reasons and restricts worktree comparison to relevant removal paths.

    100k-file rm --dry-run --cached median: 113.4 ms → 25.5 ms (4.4× better)
  • ›grit commit on split-index repo with index.skipHash/manyFiles leaves index unreadable by git#1841be736c05

    Shared-index rebuilds now write a real checksum even when the main index is configured to skip its checksum, and derive the shared filename/link OID directly from that trailer. The same change replaces a quadratic shared-entry position scan with binary search and adds Git round-trip coverage for commit, write, reuse, zero-link, and corruption paths.

    100k-file forced split-index rebuild: 18.0 seconds median of 5 → 0.121 seconds median of 5 (149× better)
  • ›post-checkout, post-merge and post-rewrite hooks never run on switch, merge, rebase or commit --amend#1816da3bd7ce

    This adds advisory post-operation hook dispatch to the library and wires switch, merge, amend, rebase, history rewrites, and pull through it. The round-two correction preserves the rebase onto commit explicitly so post-checkout receives Git's old-tip→onto arguments, including conflict stops and pull rebases.

  • ›grit fsck on git/git: 258 s and 12 GB peak (git fsck: 35 s, 564 MB); cargo 2.5 GB vs 234 MB#1825cf333d0f

    Pack fsck now resolves delta trees depth-first and inspects each inflated object immediately, retaining only compact connectivity facts instead of all object bytes and tree names. The existing index-pack resolver becomes a shared internal walker, pack input is memory-mapped, and the benchmark suite now rejects large fsck RSS regressions.

    Cargo fsck median wall time (origin/main to branch): 3.4 seconds → 1.4 seconds (2.4× better)Cargo fsck median peak RSS (origin/main to branch): 2,447,668 KiB → 206,556 KiB (12× better)Cargo fsck wall time (system git to branch): 5.9 seconds → 1.4 seconds (4.2× better)Cargo fsck peak RSS (system git to branch): 232,316 KiB → 206,556 KiB (12% better)
  • ›grit commit says 'working tree clean' but leaves a staged-then-deleted new file in the index (AD)#1842f94e6951

    When commit auto-staging makes an AD or reverted-MM index state cancel back to HEAD, grit now writes that cleaned index before returning NothingToCommit. Dry-run remains non-mutating, and successful commit behavior is unchanged.

  • ›grit switch away from a detached HEAD silently strands new commits (no warning, unlike reset --hard)#1852aa22e1e8

    Switch now detects commits that would be left reachable only through reflogs when moving away from a detached HEAD. The library returns bounded typed summaries, while human, JSON, and Markdown CLI output warns users and gives a recovery branch command; exactness is preserved under skewed commit dates.

    Detached switch, 302 commits/48 refs, no commit-graph median: 3.0 ms → 3.7 ms (19% worse)Detached switch, 302 commits/48 refs, commit-graph median: 2.9 ms → 3.2 ms (9% worse)
  • ›grit add of untracked files is quadratic in directory count: 4000 dirs = 93 s, 1 GB RSS (git add -A: 0.26 s)#1854d1ea53f0

    This change makes staging untracked trees linear in directory count. Add now requests one full untracked listing in the common case, while the remaining collapsed-directory cache path walks each collapsed subtree once and applies the original pathspec filter.

    2,000 flat directories: grit wall time: 21,504 ms → 45.8 ms (469× better)2,000 flat directories: grit peak RSS: 277,584 KiB → 15,812 KiB (18× better)2,000 flat directories: grit/git time ratio: 622.3 x → 1.3 x (469× better)49,000 nested files: grit wall time: 17,954 ms → 675.8 ms (27× better)
  • ›First-push/pull/merge errors are dead ends: no remote, auth, upstream and merge --continue give no next step#18532a0855e6

    Push, pull, clone, merge continuation, and worktree/branch switching now preserve typed failure data through grit-lib and render concrete next steps in all three CLI output modes. URLs no longer expose smart-HTTP discovery suffixes, dirty/conflicted paths become structured fields, and command-specific exit statuses survive error wrapping.

    push without remote, release median (60 runs): 1.9 ms → 1.7 ms (9% better)up-to-date push, release median (60 runs): 2.2 ms → 2.0 ms (7% better)
  • ›grit diff --json: file->symlink change is "modified" in worktree/staged diffs but "type_changed" in commit diffs#1843b14385a8

    Unifies file↔symlink changes under TypeChanged across tree, index, worktree, status, stash, and checkout-guard diff paths. The round-2 revision specifically prevents WorktreeVsHead from reporting an intermediate staged index mode when the actual worktree has changed again, so its status and modes now describe HEAD versus the final filesystem state.

    50k-file worktree-vs-HEAD diff median: 24.3 ms → 24.4 ms (1% worse)
  • ›grit repack/gc on rust-lang/cargo: 18-111s and 3.5-3.7 GB RSS (git repack -adf: 5-6s / 450 MB, git gc: 1.2s)#169050fbc094

    Repacking now copies existing packed objects and deltas into a streamed output pack by default, while explicit --force performs a bounded streaming delta search. The round-2 correction preserves an OFS delta even when its base row in the source pack is unclaimed because an older pack supplied the selected copy.

    cargo-shape default pack bytes (system git → reviewed grit): 4,539,076 bytes → 4,542,367 bytes (0% worse)cargo-shape forced pack bytes (system git → reviewed grit): 4,540,860 bytes → 4,546,188 bytes (0% worse)
  • ›grit-lib Odb::write of identical content from several threads fails ~35% with Io(NotFound) (fixed tmp_<oid> name)#1875c8c92a70

    Loose-object writers no longer share a temp filename when publishing identical content. All loose write paths now create exclusive pid/counter temp files, stream or copy compressed bytes into them, and converge successfully when another writer has already published the destination.

    20,000 distinct small loose writes, five-run median: 737.7 ms → 693.0 ms (6% better)Branch loose-write speed versus system git hash-object: 1,253 ms (system git before, branch after) → 693.0 ms (system git before, branch after) (81% better)
  • ›grit fetch/pull with no remote ignore branch.<name>.remote and always use 'origin' (push honors it)#18563462ebd1

    No-argument fetch and pull now resolve the checked-out branch’s configured remote instead of silently contacting origin. The round-2 revision handles local-tracking branches explicitly: fetch retains the prior useful behavior of contacting origin, while pull integrates the local branch without a network transfer.

    No-op fetch median, origin/main to branch: 1.9 ms → 2.1 ms (7% worse)No-op pull --ff-only median, origin/main to branch: 2.1 ms → 2.1 ms (3% worse)No-op fetch median, system Git to branch grit: 5.7 ms → 2.1 ms (2.8× better)No-op pull --ff-only median, system Git to branch grit: 8.0 ms → 2.1 ms (3.7× better)
  • ›grit gc midx writes multi-pack-index v2 by default (ignores midx.version=1); git <= 2.43 then dies on every command#18760d341585

    Non-compaction multi-pack-index writes now default to format v1, while callers can explicitly request v2 and repository maintenance honors `midx.version`. Version selection is centralized and validated before dry-run returns, preventing both incompatible default files and invalid dry-run requests from silently succeeding.

    Cargo-clone MIDX median wall time: 0.171 seconds → 0.139 seconds (23% better)Cargo-clone MIDX one-run peak RSS: 93,692 KiB → 93,340 KiB (0% better)
  • ›grit gc repack --bitmaps on an already-packed cargo clone takes 60 s (git repack -adb: 1.6 s, grit repack: 2.2 s)#18774e679b4d

    This change makes pack bitmap generation inspect packed entry headers, parse commits once, and walk trees by entry mode without inflating blobs. It also reuses unwritten fork-point bitmaps and performs closure checks during the bitmap walk, preserving Git-readable output while removing nearly all bitmap-writer time on a cargo-sized pack.

    cargo-sized PackBitmapWriter write: 56.5 seconds → 0.861 seconds (66× better)
  • ›grit-lib RevWalk::walk("A...B") silently returns the wrong commits (3 instead of 5); RevWalk::commits is right#18790ce54f8e

    RevWalk::walk now treats a top-level `A...B` as a symmetric difference instead of silently walking the wrong history. A shared parser distinguishes that operator from literal ellipses in `:/message`, `^{/message}`, and `rev:path`, and unsupported one-string walk inputs now return typed errors.

  • ›No automatic maintenance: grit commit never runs gc --auto, loose objects pile up past gc.auto (7229 after 1500 commits)#18594db386a9

    Adds automatic repository maintenance after successful object-writing commands, using typed grit-lib decisions and a thin CLI launcher. Round 2 makes loose-object auto-repack preserve Git semantics by packing only reachable objects, pruning expired unreachable objects, and correctly detaching/matching gc processes on Windows.

    Median 200-commit latency: origin/main vs branch: 4.0 ms/commit → 4.1 ms/commit (2% worse)Automatic gc runtime on 9,604 loose objects: system Git vs branch grit: 0.515 seconds → 0.496 seconds (4% better)Automatic gc pack size on 9,604 objects: system Git vs branch grit: 993 KiB → 2,861 KiB (65% worse)
  • ›grit add/commit of a 1 GB file peaks at 2.6-3.0 GB RSS (git streams it: 5 MB)#185022a99e32

    Large regular files can now be staged without loading or copying their full contents in memory. The library streams identity-clean files into Git-compatible single-object packs and safely publishes checksum-named pack/index pairs even when writers race or resume an interrupted install.

    600 MiB add peak RSS, origin/main to branch: 1,247,248 KiB → 11,808 KiB (106× better)600 MiB add wall time, origin/main to branch: 1.6 seconds → 0.600 seconds (2.7× better)600 MiB repository .git size, origin/main to branch: 7,717,722 bytes → 4,302,514 bytes (79% better)
  • ›grit gc in a shallow clone writes a commit-graph that makes git fsck fail (rc 16: 'parent list ... is too long')#18800bd9f415

    This change makes commit-graph writes detect repositories whose effective parent lists differ from commit objects and skip safely instead of producing a graph Git rejects. It also removes stale monolithic and split graphs so a subsequent gc repairs repositories affected by older Grit versions, and reports the typed reason through the library and all CLI output modes.

    gc commit-graph median, 15 paired release runs on 13,055 commits / 11,002 refs: 2,972 ms → 2,983 ms (0% worse)gc commit-graph median peak RSS on the same corpus: 204.4 MiB → 204.2 MiB (0% better)
  • ›Conflicted `stash pop`: status hints `grit merge --continue` (no merge) and `restore <path>` says pathspec unknown#186096e4867a

    Plain restore now recognizes matched unmerged index entries and returns a typed, structured conflict error before changing any files. Conflicted stash pop/apply and dirty switch failures now tell users how to proceed, while the CLI remains thin over library behavior.

    restore . median, 2,355 tracked files: 26.4 ms → 26.5 ms (0% worse)restore one path median: 3.6 ms → 3.6 ms (1% worse)
  • ›grit merge: rename/rename (a->b vs a->c) merges cleanly and duplicates the file; git reports a conflict#1878680af8ae

    The tree merge now recognizes when both sides rename one base file to different names, preserves Git's stage-1/2/3 index layout, and reports a typed rename_rename conflict instead of silently committing duplicate files. Merge checking, real merge, pick, rebase, human output, JSON, and resolution documentation all use the corrected result.

    10k-file merge-check median: 816.4 ms → 800.1 ms (2% better)merge-check slowdown versus system git: 65.2 x → 63.9 x (2% better)
  • ›grit merge is quadratic in repo size: 20k files changed on both sides 142 s (git 3.1 s); 2k of 200k files 118 s#1881746d6fe4

    Tree merges now create one lazy attribute-rule context for the entire operation, load the current index at most once, and resolve each path's attributes once before applying union/binary/content behavior. A regression test, wide content-merge benchmark, committed baselines, matrix registration, and benchmark documentation accompany the hot-path fix.

    2k both-sides merge wall time (grit main → branch): 2.1 seconds → 0.242 seconds (8.7× better)2k both-sides merge peak RSS (grit main → branch): 20,236 KiB → 20,416 KiB (1% worse)2k both-sides merge wall time versus system Git reference: 0.348 seconds (Git reference → branch) → 0.242 seconds (Git reference → branch) (44% better)
  • ›grit commit (auto-stage) stats every tracked file twice: 1-file commit in 200k-file repo 561 ms vs git 168 ms#1884279719ff

    Auto-stage commit now reuses the index stat data produced by its staging pass instead of walking every tracked file again after creating the commit. The change is confined to grit-lib behavior plus regression coverage, rustdoc, and a recorded benchmark baseline.

    200k-file auto-stage commit mean: 456.2 ms → 392.3 ms (16% better)200k-file auto-stage statx calls: 413,138 calls → 208,896 calls (98% better)200k-file clean allow-empty commit mean: 432.3 ms → 358.7 ms (21% better)
  • ›Windows: hooks get GIT_INDEX_FILE=\\?\C:\... (git sees empty index) and a PATH without sh's unix tools#1811f7c22601

    Windows hooks now receive ordinary drive/UNC repository paths instead of verbatim `\\?\` paths, so Git inside a hook reads grit's actual index. Shell hooks resolved from Git for Windows also receive that installation's MinGW and MSYS tool directories first on PATH, restoring grep, sed, env, and similar tools.

  • ›CompositeStore::read/read_info/read_into turn a child's CorruptObject/Zlib error into Ok(None) (object 'missing')#1891287deb3e

    Composite object reads now remember the first damaged-copy error while continuing through later backends. A later healthy replica still wins, but if every remaining backend misses or is damaged, callers receive the first corruption error rather than a false “object missing” result.

  • ›Windows: file-in-use errors say only 'I/O error ... (os error 32)': no file name, no hint, doubled text#1915d6287408

    Worktree file failures now carry a typed repository-relative path and operation, producing actionable in-use diagnostics rather than anonymous or duplicated OS text. Checkout, staging, diff, batched reads, stash, and CLI structured errors use that context, while failed stash cleanup removes the newly created stash tip.

  • ›grit commit swallows unknown flags as the commit message: `commit -q` / `--bogus` creates a commit named '-q'#19064b11c2ab

    Unknown options in `grit commit` can no longer be consumed as the positional commit message and silently commit the worktree. The command now also supports `-q/--quiet` for successful human output while retaining JSON and Markdown output.

  • ›grit repack fails 'object not found', gc fails with loose objects, fsck reports missing objects in any shallow clone#1882f7224da4

    This change makes every maintenance and integrity history walk honor shallow boundaries, including from linked worktrees. It centralizes the shared `shallow` file path through `commondir`, then threads the resulting boundary set through repack, gc/prune, fsck, commit/revision walks, and bitmap generation.

    Depth-200 loose shallow gc: system Git vs reviewed grit: 0.088 seconds → 0.052 seconds (69% better)
  • ›No way to put a received pack into a custom ObjectStore: fetch/clone/unbundle impossible, ingest is files-only#1892ca4bdb17

    Received pack streams can now be expanded directly into any writable object store, including full, OFS-delta, REF-delta, thin, SHA-1, and SHA-256 packs. Repository-level received-pack ingestion and bundle unbundling automatically use this path for custom primaries, while files-backed repositories retain pack installation.

    Files-backed unpack median on 8,043-object delta pack: 0.379 seconds → 0.377 seconds (1% better)
  • ›grit gc of cargo with 195k loose objects: 400 s, 5.4 GB RSS, 2.4x larger pack (git gc: 32 s, 100 MB)#18832da2efae

    This replaces pack-objects’ eager all-payload planner with the shared streaming delta search used by repack. Upload-pack, push, bundle, and local clone now search blobs, trees, and commits lazily, preserve reusable packed deltas, emit bases first, and produce packs close to Git’s size while sharply reducing memory.

    Warmed local clone elapsed: 1.2 seconds → 0.750 seconds (63% better)Warmed local clone peak RSS: 108,700 KiB → 33,704 KiB (3.2× better)Warmed local clone pack size: 4,116,608 bytes → 1,309,571 bytes (3.1× better)Pack size relative to system git: 3.2 × git → 1.0 × git (3.1× better)
  • ›GIT_ASKPASS / core.askPass ignored: token-injection CI flows fail with 'credentials required but unavailable'#1908600b2747

    Adds non-interactive askpass credentials to the library HTTP transport after credential helpers cannot complete a username/password. It supports GIT_ASKPASS, core.askPass, and SSH_ASKPASS, safely carries URL userinfo into credentials, wires repository/process environments through the default HTTP factory, and documents the behavior.

  • ›Materializing a 350 MB blob (restore/switch/clone) takes 10 s and 1 GB RSS (git: 0.95 s, 93 MB)#1849c01929da

    Large blobs can now move from loose or whole packed objects to work-tree files without materializing the full payload in memory. The path also avoids redundant conversion/hash copies, validates corrupt streams before installing files, and preserves selective ODB backend error propagation.

    349 MB restore wall time: 7.7 seconds → 0.935 seconds (8.3× better)349 MB restore peak RSS: 1,032,404 KiB → 10,584 KiB (98× better)349 MB switch wall time: 5.9 seconds → 0.630 seconds (9.4× better)349 MB switch peak RSS: 1,033,172 KiB → 11,536 KiB (90× better)
  • ›Injected RefStore ignored by RevWalk --all, `:/text` and worktrees().add (read <git_dir> refs from disk)#1889597d42ec

    Repository-scoped revision queries now see injected ref backends instead of silently reopening an empty files backend. Linked worktree add/list/remove likewise use injected refs and objects, including repositories whose git directory is outside the work tree, while files/reftable repositories retain their existing disk-backed path.

    revwalk --all median on 13,136-commit repository, merge base to branch: 91.0 ms → 91.3 ms (0% worse)system git rev-list --all --count versus branch grit-lib revwalk on same repository: 83.0 ms → 91.3 ms (9% worse)
  • ›diff --patch-text omits binary content, so `git apply` rejects any patch that touches a binary file#19130152853a

    Binary files in `grit diff --patch-text` now carry complete `GIT binary patch` bodies and full content ids instead of header-only entries. The library resolves each side lazily from caller bytes, the ODB, or normalized worktree content, while the CLI only selects the source.

    Mixed text/binary dist patch generation: system git vs branch grit: 24.1 median ms → 15.0 median ms (60% better)
  • ›grit diff between two tags on git/git peaks at 250-503 MB (git 110-155 MB): whole patch buffered before output#19167085b9ed

    Streams human and Markdown commit-range patches through a bounded ordered worker pipeline instead of retaining every rendered file diff. It also streams patch-text chunks in order, preserves change detection for metadata-only/streamed outcomes, and keeps Markdown output byte-equivalent to collected rendering.

    git/git v2.46.1..v2.56.0 human median peak RSS: 288.3 MiB → 188 MiB (53% better)git/git v2.0.0..v2.56.0 human median peak RSS: 570.3 MiB → 247.3 MiB (2.3× better)git/git v2.46.1..v2.56.0 human median wall: 1,419 ms → 1,342 ms (6% better)git/git v2.0.0..v2.56.0 human median wall: 1,133 ms → 1,145 ms (1% worse)
  • ›upload-pack stateless-rpc rejects reachable non-tip wants ('not our ref'): fetch fails while others push#1939d7c6e950

    Smart-HTTP fetches can now request objects that were reachable when refs were advertised but ceased being tips before the stateless POST. The round-3 correction also handles moved annotated tags whose targets are reachable commits, trees, or blobs instead of assuming every peeled target is a commit.

  • ›tag --json / tag -n: subject of a signed annotated tag includes the PGP signature block (cargo's 0.1.0 etc.)#19400eca9a15

    Annotated signed tags now show only their human subject in tag listings, even when signature armor immediately follows the message. The full stored annotation remains available unchanged.

  • ›grit-lib: no cancellation for clone/fetch/checkout/status, CLI-only uncommitted-diff logic, models not serializable#17043827e69f

    Adds cancellation propagation through clone checkout, local object copy, switch, reset, history rewrite, and checkout execution; adds opt-in serialization for porcelain-facing models. Round 3 chunks parallel file deletion and adds a backward hard-reset regression, but one populated wholesale directory can still be fully deleted before cancellation is observed.

  • ›grit-lib: no uniform way to tell a cancelled operation; Cancelled is nested differently in every error type#194399a2c934

    Adds a common `is_cancelled()` query to grit-lib's top-level and operation-specific error types, so embedders can detect user cancellation without matching each wrapper shape. It also publicly exposes the existing `RemoteResult<T>` alias and documents the intended remote error-query pattern.

  • ›grit push of a whole repo over smart HTTP sends an undeltified pack: cargo 28s/1.07GB (git 2.3s/163MB)#1941a085c2e0

    Wire pushes now choose the same configured delta-capable pack modes as local pushes instead of assuming that a small number of ref tips means a small object closure. Initial pushes build full delta-compressed packs; incremental pushes remain thin and honor the server's OFS_DELTA capability.

    Synthetic smart-HTTP push elapsed: 12.5 seconds → 0.494 seconds (25× better)Synthetic smart-HTTP push peak RSS: 979,404 KiB → 115,004 KiB (8.5× better)Received pack size: 335,668,484 bytes → 19,427,346 bytes (17× better)
  • ›upload-pack thin-pack incremental fetch (1 new commit) on cargo: 26 s / 1.6 GB RSS (git: 25 ms / 11 MB)#19384906b780

    This replaces upload-pack's eager peer object closure with compact negotiation boundaries and bitmap-backed peer reachability, then limits thin-pack base search to name-hash candidates and have-tip trees. A second fix paints commits reachable from haves so divergent tips stop at shared history without restoring the multi-gigabyte object closure.

    Cargo one-commit thin-pack wall time (median of 3): 29.6 seconds → 0.213 seconds (138× better)Cargo one-commit thin-pack peak RSS (median of 3): 1,631,892 KB → 40,364 KB (40× better)
  • ›Windows: first grit status after clone/switch takes 3-13 s on 3k files (git 0.2 s); later runs 0.17 s#1914fc4afe51

    This makes first status after clone, switch, or hard reset verify racily clean files in parallel instead of opening and hashing them serially. It also removes the redundant per-file lstat by applying the stat data already gathered by the worktree scan, while retaining the same whole-second racy rule and one-time index rewrite.

    3,000-file all-racy first status median: 16.0 ms → 10.9 ms (47% better)3,000-file all-racy status / system Git median ratio: 1.3 ratio → 0.856 ratio (47% better)
  • ›Parallel `grit clone` into the same new dir: ~half the time every process fails and the dir is deleted (git: one wins)#1935eec83c34

    Clone now reserves a new destination atomically before repository initialization, so concurrent clones cannot all race into the same directory or delete the eventual winner. It preserves the established error precedence for already non-empty destinations while ensuring invalid options do not create artifacts.

  • ›Parallel grit fetch into one repo: all but one process fail with 'I/O error: Permission denied (os error 13)'#19052824ad39

    Makes local object copying and pack installation safe when multiple fetch processes publish the same content-addressed files. Writers use unique staging and no-clobber publication; losing a race is success, and failed/cancelled installers never remove files another successful installer may already depend on.

    Optimized local fetch median, main to branch (5 runs): 0.294 seconds → 0.295 seconds (0% worse)Optimized local fetch median, system git to reviewed grit (5 runs): 4.9 seconds → 0.295 seconds (17× better)
  • ›upload-pack: --depth 1 on a repo with annotated tags sends full history; --no-single-branch fails#173468344c5a

    Shallow upload-pack requests now peel annotated tag wants to commits before calculating depth, so emitted shallow borders are always commits. Include-tag selection now obeys both send-side and client-side shallow grafts, preventing tags below the cut from pulling full history into a shallow pack.

    Depth-1 commits sent on 10k-commit tagged fixture: 9,001 commits → 1 commits (9001× better)Depth-1 packed objects on 10k-commit tagged fixture: 27,013 objects → 4 objects (6753× better)Depth-1 clone median wall time (5 dist-profile samples): 0.203 seconds → 0.013 seconds (15× better)
  • ›grit pull --rebase conflict leaves no state to resolve, while grit rebase stops for --continue#1909bf30b590

    Conflicting `pull --rebase` now enters the same resumable rebase session as direct rebase, leaving markers, unmerged index entries, and operation state for continue/abort/skip. Its human, JSON, and Markdown results identify the paused operation and stopped commit; critically, `pull --check` now reports only the prediction and does not claim an operation is in progress.

other changes11
  • ›Speed: branch/merged@many-refsf37fe03e

    Normal reads of sorted `packed-refs` now binary-search the mapped bytes instead of decoding and indexing every record at open time. Unsorted files remain lazy linear scans, while `grit fsck` detects a header that falsely claims sorted order; compatibility and malformed-input tests cover the new behavior.

    branch/merged mean, 400 alternating runs (git 1.221 ms): 2.5 ms → 2.3 ms (11% better)branch/list mean, 300 alternating runs (git 1.107 ms): 2.2 ms → 1.9 ms (13% better)branch/list-json mean, 300 runs (git 1.289 ms): 2.5 ms → 2.2 ms (12% better)branch/merged-ref mean, 300 runs (git 1.136 ms): 2.6 ms → 2.4 ms (12% better)
  • ›Speed: remote/remove@many-refsffb6822e

    Large files-backend ref deletion batches now prune packed refs in one rewrite and persist each update without repeatedly rebuilding the whole batch state. If a later commit-time recheck fails, records for the failed and unreached refs are merged back under the held packed-refs lock, preserving exact ref and peel records.

    remote/remove@many-refs wall time (origin/main to branch, median of run medians): 34,353 ms → 349.4 ms (98× better)remote/remove@many-refs peak RSS (origin/main to branch, maximum sampled): 25.4 MiB → 23.0 MiB (10% better)remote/remove@many-refs branch vs system git wall time: 761.3 ms (git then grit) → 349.4 ms (git then grit) (2.2× better)remote/list@small attributable wall time (branch parent to branch): 3.3 ms → 3.3 ms (0% better)
  • ›Speed: show/head@git-gitbca77365

    This removes four recurring costs from `grit show` and other object-reading hot paths: unused human/Markdown ref decoration scans, one-item thread-policy/pool setup, per-read empty-tree hashing, and unnecessary config-path canonicalization. It adds focused regression tests and committed before/after benchmark evidence without changing public APIs, CLI output, or Git formats.

    show/head@git-git wall median (system git 1.630 ms): 3.0 ms → 2.6 ms (15% better)show/head@git-git RSS median: 16,904,192 bytes → 16,252,928 bytes (4% better)show/head@big-blobs wall median (system git 1.273 ms): 2.6 ms → 2.3 ms (11% better)show/head@big-blobs RSS median: 15,409,152 bytes → 14,669,824 bytes (5% better)
  • ›Speed: stash/push-pop@synth-10keb67993a

    This change removes repeated config/attribute loading, redundant worktree diffs, whole-index scans, unnecessary blob inflation and serial file writes from stash push/pop. It also fixes deferred-write directory pruning and ensures internal stash commits remain unsigned, matching Git.

    stash push-pop grit wall time: 331.4 ms median → 147.3 ms median (2.3× better)stash push-pop ratio vs same-session system Git: 1.6 × Git → 0.723 × Git (2.3× better)stash push-pop grit peak RSS: 23.6 MB → 23.4 MB (1% better)stash push-pop RSS ratio vs same-session system Git: 1.3 × Git → 1.3 × Git (1% better)
  • ›Quality #31: Move logic into grit-libfce0b9c5

    Replay outcomes now carry subjects and change IDs needed by pick/revert callers, including records reconstructed after a stopped sequence. The CLI maps those typed outcomes directly instead of re-reading commit objects, and pick/revert share one empty-policy argument type.

    pick-range-20 order-balanced median: 88.1 ms → 89.7 ms (2% worse)revert-single order-balanced median: 75.1 ms → 67.6 ms (11% better)revert-mainline order-balanced median: 76.2 ms → 68.1 ms (12% better)pick-range-20 branch vs system git: 122.3 ms → 89.7 ms (36% better)
  • ›Speed: log/json-100@git-gitcfc18088

    This change eliminates duplicate commit reads while building log pages and trims fixed ref, rev-parse, and pack-index lookup costs. It also corrects Linux benchmark timing so sub-millisecond improvements are measured at child exit rather than rounded to the next RSS polling tick, and records paired benchmark evidence.

    log/json-100 wall (system git 2.371 ms): 3.7 ms → 3.5 ms (4% better)log/json-100 peak RSS: 18.6 MiB → 18.6 MiB (0% worse)log/oneline-100 wall (system git 2.346 ms): 3.1 ms → 3.0 ms (3% better)log/json-filter wall (system git 1.532 ms): 3.6 ms → 3.5 ms (2% better)
  • ›Quality #32: Deduplicate81f8a1d5

    This change adds one typed Repository::head_commit API and replaces repeated HEAD-to-commit matching across history operations and tag handling. It also centralizes Error-to-HistoryError conversion and fixes rebase exec restoration so injected and on-disk ref sources cannot produce a mismatched HEAD/index/worktree state.

    history reword-1000 grit mean: 152.0 ms → 140.1 ms (9% better)history reword-1000 rebase-baseline grit mean: 137.6 ms → 146.8 ms (6% worse)history update-refs=head grit mean: 143.5 ms → 140.5 ms (2% better)history drop-100 grit mean: 127.9 ms → 127.9 ms (0% worse)
  • ›Speed: reset/mixed@synth-10kedec764b

    Mixed reset now reuses valid cache-tree subtrees and clean index stat rows instead of rereading every tree and hashing every tracked file. Hard reset uses the same tree walk and writes the resulting TREE extension, while the shared seeded-index path also benefits partial commit and stash.

    reset/mixed@synth-10k median (system Git 8.93 ms): 43.6 ms → 11.1 ms (3.9× better)reset/mixed@synth-10k peak RSS: 21.0 MiB → 19.4 MiB (9% better)reset/hard@synth-10k median (system Git 16.46 ms): 21.8 ms → 20.0 ms (9% better)reset/hard-delete-heavy@wide-48k median (system Git 215.87 ms): 233.8 ms → 214.5 ms (9% better)
  • ›Perf regression 2026-10-24: add/modified@big-blobs, add/update@synth-100k, clone/local@synth-10k +8591df8b2

    This change removes redundant reads from tracked staging and paged log walks, bounds and parallelizes large tracked restaging, and makes protocol-v2 have negotiation lazy. It also records reproducible benchmark evidence and adds system-Git compatibility coverage for chunked add -u.

    add/update@synth-100k (system git ≈112 ms): 219.2 ms → 113.0 ms (94% better)add/modified@big-blobs (system git ≈217 ms): 609.1 ms → 233.7 ms (2.6× better)add/all@synth-100k (system git ≈161 ms): 266.4 ms → 152.4 ms (75% better)
  • ›Quality #33: Simplify96ffd3d7

    Deletes four unused public methods on the repository-scoped diff handle and the private implementation chain reachable only through them. It also removes the never-produced merge `set_upstream` outcome from CLI rendering, v2 schema, and documentation; pull retains its separate, live upstream-adoption result.

    diff/staged@synth-100k grit median: 50.5 ms → 49.6 ms (2% better)diff/stat@synth-100k grit median: 105.8 ms → 107.1 ms (1% worse)diff/worktree@synth-10k grit median: 8.2 ms → 7.9 ms (4% better)merge/clean@synth-10k grit median: 69.7 ms → 68.9 ms (1% better)
  • ›Speed: pull/check@git-gitce3fb949

    Protocol-v2 fetch negotiation now walks local history lazily in growing rounds instead of inflating the whole history before the first request. It also handles replayed ACKs without falsely resetting the in-vain counter and seeds negotiation from remote-tracking refs, with protocol tests, benchmark evidence, and documentation included.

    pull/check@git-git median wall (12 main, 16 branch runs): 823.2 ms → 57.6 ms (14× better)pull/check@git-git peak RSS: 67,522,560 bytes → 50,200,576 bytes (35% better)pull/check system-git control median: 129.3 ms → 130.7 ms (1% worse)pull/fast-forward@git-git median wall (10 main, 16 branch runs): 886.9 ms → 56.0 ms (16× better)
$ git log --oneline 77fd8a48..v0.5.21compare on GitHub ↗
5ec1fbe7grit-bench: add gc-loose scenarios
b43da698pack-objects: stream the delta search instead of loading every object
ca4bdb17Merge branch 'factory/ingest-pack-into-store'
f80b2e55docs: receiving packs into a custom object store
4438f0feunpack_objects: apply received packs to any object store
f7224da4Honour shallow boundaries in repack, gc, prune, bitmaps and fsck
4b11c2abMerge branch 'factory/commit-reject-unknown-flags'
deaf84becommit: reject unknown flags instead of using them as the message
d6287408Fixes #1915: report worktree file I/O failures with path and hint
ce3fb949Merge branch 'factory/pull-check-speed'
287deb3eMerge branch 'factory/composite-corrupt-error'
60825cdddocs: describe lazy v2 fetch negotiation and its git.git benchmark
33f85c72fetch: negotiate protocol v2 in lazy rounds instead of walking all history first
8891e763odb: report a damaged copy from CompositeStore instead of calling the object missing
f7c22601hooks: give Windows hooks plain repository paths and Git's tool PATH
279719ffMerge branch 'factory/commit-autostage-single-scan'
d9639b92bench: record daily commit rows after the single-scan auto-stage commit
7eed7564commit: scan the work tree once when auto-staging
96ffd3d7Merge branch 'factory/dead-diff-handle-paths'
591df8b2bench: record the 2026-10-24 regression rows before and after
64cc3a7dfetch: walk local haves only as far as the v2 rounds send
150e2433log: read each listed commit once on the paged log walk
91da6934add: read each restaged file once
b490700cadd: keep tracked restage parallel on large indexes
746d6fe4bench: add a both-sides content-merge daily scenario and document the merge speedup
f8eb35ddmerge: load attribute rules once per tree merge, not per merged path
fc6f8433diff: delete Diff handle methods nothing calls
943979ecmerge: drop the never-emitted set_upstream result
680af8aeMerge branch 'factory/rename-rename-conflict'
0428b469merge: stop re-merging a base path the current branch renamed away
d073c453history: report conflicted paths in sorted order
4403bc04merge: report rename/rename (1to2) as a conflict instead of keeping both copies
96e4867aMerge branch 'factory/stash-pop-conflict-hints'
f24fa9e2restore: report unmerged paths as a guided unmerged_paths error
af0a2a81stash: tell users how to finish a conflicted pop or apply
dae9a11erestore: refuse unmerged paths instead of calling them unknown
0bd9f415Merge branch 'factory/shallow-commit-graph'
60b78067gc: skip the commit-graph in shallow, grafted and replaced repositories
43e1c992commit-graph: refuse to encode a parent that is not in the graph
22a99e32Merge branch 'factory/stream-big-blobs'
a3e9b714grit-bench: add-big-file scenario with time and peak RSS
62dd2bf9grit-lib: stream files above core.bigFileThreshold into a pack when staging
4db386a9Merge branch 'factory/gc-auto'
fa73db23grit: run automatic maintenance after object-writing commands
5e530ab0maintenance: add gc_auto with Git's gc.auto heuristics and gc.pid lock
0fd66e80tests: pass the cancel argument status now takes
0ce54f8eMerge branch 'factory/revwalk-walk-symmetric'
4e679b4dMerge branch 'factory/bitmap-writer-speed'
29494fccdocs: describe the bitmap writer's walk and its cargo benchmark
0dc5e9capack_bitmap: write bitmaps without reading blobs or re-walking history
edec764btest: use each reset fixture's target commit
f2cde734reset: format rebased import ordering
0a61eb6fdocs: benchmarks for reset reusing the index it replaces
d44344c4index_from_tree: hand the walked rows to the index instead of re-pushing them
b78fed2breset --hard: flatten the target tree from the cache-tree and keep it
2307abc5reset: rebuild the mixed-reset index from cached stat data and cache-tree
81f8a1d5Merge branch 'factory/head-commit-dedupe'
0d341585Merge branch 'factory/midx-default-v1'
be4b2efagc midx: document midx.version and add a git round-trip regression test
b433cf43midx: write v1 by default and honour midx.version
3462ebd1fetch, pull: default to the current branch's remote instead of origin
5d820418state: add Repository::head_commit and use it for every HEAD-commit lookup
e928c984history: convert library errors with one From impl instead of per-module adapters
cfc18088public-api: refresh log API snapshot ordering
118960fdbenchmarks: record log pages on git.git against current main
5fa57a52grit-bench: time runs at child exit instead of at the next RSS poll
fe01cff3log: hand the commits the walk already read to the output rows
57501c7epacked-refs: decode record ids while validating them
242f98b9rev-parse: peel from object headers and read each dwim candidate once
7c0aaac1pack index: skip the forward scan when a monotonic probe jumps past it
c8c92a70Merge branch 'factory/loose-write-unique-tmp'
b8c80b9adocs: note that concurrent object writes are idempotent
438234f7odb: publish loose objects through unique temp files
50fbc094Merge branch 'factory/repack-delta-reuse'
502e769erepack: guard time, memory and pack size against git
379a0b73repack: balance the delta search and keep other packers' walk lean
a400e42brepack, gc repack: add --force to recompute every delta
f964cb41repack: copy stored pack entries and search deltas in a bounded window
b14385a8Merge branch 'factory/diff-typechange-status'
903d8b5bdiff: report file <-> symlink changes as type_changed in every diff mode
2a0855e6Merge branch 'factory/first-push-error-hints'
10df37cbgrit: say what happened and the next command for first-push, pull and merge errors
7a7e6f3bgrit-lib: typed errors for missing remotes, credentials and dirty worktrees
d1ea53f0Merge branch 'factory/add-untracked-linear'
f0e6c89badd: stage untracked directories in one walk instead of one walk per directory
1a64baa5grit-bench: add-many-dirs suite for initial add . over many directories
aa22e1e8grit switch: warn about commits left behind on a detached HEAD
8866ead9rev_parse: tell a ref names a commit without inflating it
933ae512orphans: stop the left-behind walk early when generations allow it
469185f6switch: report commits left behind when leaving a detached HEAD
f80126c3porcelain: share the orphaned-commit walk between reset and switch
fce0b9c5Merge branch 'factory/replay-outcome-subjects'
f94e6951Merge branch 'factory/commit-autostage-ad'
d02c2c66commit: persist auto-staged index when nothing is left to commit
cf333d0fMerge branch 'factory/fsck-streaming-memory'
a3b829c1pick, revert: share one --empty value enum
5d47ca21replay_sequence: report subjects and change ids so pick/revert stop re-reading commits
d309f6a8bench: refresh fsck-medium baseline after streaming pack fsck
257a021dgrit-bench: fail the fsck suite on a peak-memory regression
229134cdfsck: stream pack verification instead of holding every object
38ad6e9apack: share the depth-first delta walk between index-pack and fsck
da3bd7ceMerge branch 'factory/post-op-hooks'
6420b080docs: document post-checkout, post-merge and post-rewrite on switch, merge, commit, rebase and history
ebe95365switch: add hooks_run to the JSON schema and output snapshots
36f15732test: post-operation hooks after switch, merge, amend and rebase
2817d250hooks: run post-checkout, post-merge and post-rewrite after switch, merge, amend and rebase
be736c05Merge branch 'factory/split-index-skiphash'
84f680a7split index: give the shared index a real checksum under index.skipHash
e21fceccsplit index: look up shared-index positions by binary search
66b5719dMerge branch 'factory/rm-cached-safety'
70848dbbgrit-lib: regenerate public-api.txt snapshot
5a8f22b5rm: let --cached untrack files edited since commit
515b23f1Merge branch 'factory/thin-pack-idx-trailer'
20f4eae6tests: thin pack completion must keep pack, idx and name consistent
4b928ba1Merge branch 'factory/upload-pack-deepen-not-wanted-refs'
4c453201upload-pack: send wanted-refs for want-ref and advertise only v2 features
7fa0bad8upload-pack: resolve deepen-not ref names and exclude them correctly
5d513685docs: pack receive memory, thin-pack completion and benchmarks
c1a1ef7bbench: record peak RSS on network scenarios and add clone/full@git-git
0b2e41c9test: gate pack ingest peak RSS against git index-pack
42198ccbindex-pack: keep received packs out of RSS and complete thin packs in place
c11cb194pack index: sort entries in place and stream the .idx to disk
82e00029Merge branch 'factory/remote-unreadable-exit'
cd888451grit: exit 128 with kind remote_unreadable when the remote cannot be read
f1049a81grit-lib: report unreachable remotes as a typed Error::RemoteUnreadable
eb67993astash: never sign stash commits
d153a56cstash: reuse stat data and cache-trees in push and pop
847c5dd2diff: reuse config and attributes across worktree-vs-index content checks
b83c306eMerge branch 'factory/porcelain-api-cleanup'
8b6150c5ident: typed Signature identities and Repository::identity
5c4624e4history: move squash-message helpers out of porcelain::rebase
6d84a3d7stash: make RepositoryStash the one stash API
7fc55e26porcelain: report detached HEAD as branch None in commit and switch outcomes
ecf4ab43Merge branch 'factory/merge-any-revision'
7fa7cb5cgrit merge: accept tags, commit ids and revision expressions
e120cf11grit-lib: resolve merge sources from any revision
06a3ecfcMerge branch 'factory/ls-refs-prefix-first'
cae8d36cbench: upload-pack ls-refs and v0 advertisement on the 100k-ref fixtures
5963bcb2serve: list only the requested ref prefixes and peel on demand
34d070d1refs: skip loose refs outside a partial-name listing prefix
d52d28e4docs: filtered upload-pack serving and blobless benchmarks
6b46c7cdgrit-bench: time blobless partial clone serving
6cae56b3upload-pack: apply partial clone filters in the pack enumeration walk
91e7fa63rev-list: keep every annotated tag of a commit in object walks
0ae050c8Merge branch 'factory/foreign-rebase-guard'
ae10a062switch, status: refuse switching and report the branch during paused operations
d43ccc38rebase: recognise rebases git started and stop in a state git can finish
157a05ddKeep sparse-checkout paths out of the work tree on checkout
3b629c90tests: pass a Cancel to status() in the core.symlinks=false tests
2b10df11diff: read a plain-file symlink entry by content
98ada1f1docs: describe core.symlinks=false staging and the WorktreeModePolicy API
df27a62aindex: keep symlink entries as 120000 when core.symlinks=false
d1ffc337site: reject grit_lib paths in the docs that are not public API
d21d69a6docs: point library guides at public grit_lib paths
02082248grit add: test parallel processes and document index.lock behavior
8fdbdec0index: read the index under index.lock in add, rm and tracked staging
b7e20e46Merge branch 'factory/clone-checkout-cpu'
b064d7f6grit-bench: list clone/checkout@synth-100k in the daily matrix doc
5aff444agrit-bench: track wide-tree clone checkout in the daily matrix
8de3516acheckout: build the cache tree once for a populated-tree checkout
489f267bMerge branch 'factory/fetch-tag-follow-fast'
5d61d87bMerge branch 'factory/hook-output-capture'
a2e214e4fetch: decide tag following from pack membership, not an object walk
e26bb265hooks: hand hook output to the caller instead of printing it
245e0652rev-list, crlf: drop GIT_TRACE lines written straight to process stderr
bca77365bench: record show HEAD rows before and after
afcae9b7repo caches: skip canonicalizing when the local config path already matches
22d178b9odb: recognize the empty tree by constant instead of hashing on every read
da9beedashow: skip ref decorations unless the output renders them
8beda38adiff: run single-entry content diffs without probing the thread policy
d96ea560Merge branch 'factory/pathspec-attr-repo'
4b7e8859pathspec: honor attr macros and core.ignorecase, reject malformed attr magic
7913ac3fpathspec: load attributes once per filter pass with PathspecMatcher
e5193f3bdocs: point the pathspec guide at PathspecMatcher
fba86b83docs: attribute pathspecs in grit diff and the library matcher
7762dca6pathspec: evaluate attr magic in Repository::paths_match_pathspecs
ffb6822eMerge branch 'factory/remote-remove-batch-delete'
b430b842refs: preserve batch duplicate validation after rebase
bdff90f9refs: delete large batches in linear time on the files backend
f37fe03ebench: record branch and tag daily rows on many-refs around the packed-refs search
75c4b565refs: trust the packed-refs sorted trait and search the file in place
54659c2bfsck: test the packedRefUnsorted check end to end
b7e2bf1erefs: index packed-refs without decoding the whole file
2657344cMerge branch 'factory/revs-range-listing'
4315303brev-list: honour use_commit_graph in hide-bounded range walks
6761e2bcmerge-base: skip unreadable history behind excluded tips in range walks
48e33357rev-list: bound unlimited A..B listings by the excluded tips
b67ae9bbrevwalk: queue parents on first discovery in range walks, like git rev-list
2f4b7d09Merge branch 'factory/issue-1704-api-219d'
048229f9fix: cleanup failed clone after RemoteRefNotFound
9ac763c0bundle: stream the pack with serve settings instead of push settings
1db4632btests: complete thin packs in an isolated receiver repository
955f6e05bench: add bundle/create-all@git-git daily scenario
8147101dpack-objects: reuse on-disk deltas against bases the peer holds
cb27c72aMerge branch 'factory/commit-msg-race'
62115432Stress concurrent path commits and document the guarantees
5f464db1Build each commit on the HEAD read before its index
163e896eKeep the requested commit message unless commit-msg ran
54e0cfc1Lock the index before a partial or patch commit writes anything
37c2ba0aMerge branch 'factory/credential-helper-url'
e9b1ebb3fix: resolve add.v2.json merge conflict markers
40a739dcdocs: describe what credential helpers receive over HTTPS
cf47eff7Send credential helpers only protocol, host and repository path
1c01643fMerge branch 'factory/filter-shell-resolve'
1517538dcommand_runner: mark the inherited PATH read as a spawn-boundary exemption
82157686filters: never store unfiltered content when a required filter fails
0039f617command_runner: resolve the shell for every sh -c spawn like Git
79ab7951fix: NeverCancel at remaining test and pull call sites
d5bdd143fix: clippy on cancel threading (too_many_arguments, must_use)
10f19cd5Fix streaming repack idx rows, pack+loose deltify, and disk staging
fa97a96efix: rebase follow-ups after cancel epic merge with main
43a215a5fix: green grit-cli schema checks; index-pack cancel polls and tests
ac034b15grit-lib: cancel through index-pack; fix call sites and docs
90eb8019Clippy: merge identical pack_opts branches in maintenance repack
45d61de1Fix streaming repack reuse: fallback when delta base excluded, O(1) offset lookup
46304c20Stream verbatim repack when one pack and zero loose objects
0f694ec8Address review: explicit repack always runs; rewrite when packs exist
9735b511Fixes #1684: skip or reuse packs on gc/repack when Git would
697bdec3grit-lib: finish cancel threading for status, bundle, fetch_all
a6247a63tests: fix NeverCancel imports after fetch_remote wiring
165f6ad2tests: import NeverCancel for fetch_remote call sites
4ae8685fgrit-lib: complete cancel on push/fetch/status; refresh public-api
e2a5b074grit-lib: cancellation, typed switch/clone errors, export hunk bundle
3c2c9795Merge branch 'factory/refstore-backend-authoring'
8f4cdfa0grit-lib: update the public API snapshot
90ae570cdocs: writing a ref backend, and opening with no git directory
6ebdae47repo: open with injected backends without a git directory layout
fccbc9edtest-support: public RefStore conformance suite
49bc6432refs: public backend helpers for custom RefStore implementations
17c00f9fMerge branch 'factory/odb-contains-result'
45bb088dPropagate object store failures from existence checks in decision paths
3ce84b1fodb: add fallible Odb::contains next to the lenient exists probes
8e9e4e09Merge branch 'factory/global-repo-options'
05cab44bskill: describe -C and -c without spelling them as grit commands
0a9f62cacli: global -C, -c, --git-dir, --work-tree and -q on clone and push
06069d64environment: typed command-line config overrides and git-dir/work-tree builders
51496d8fMerge branch 'factory/upload-pack-streaming'
b859569bdocs: describe pack.depth 0, base ordering and batched pack writes
d1450e53upload-pack: test full-clone serving against system git
8c8a1d76pack-objects: encode pack batches on long-lived workers
7a4761c4pack-objects: report verbatim reuse from the streaming pack writer
3a6ba73bpack-objects: write delta bases before their targets
f90f1424upload-pack: keep sending on-disk deltas when pack.depth is 0
3d58c3b1pack-reuse: resolve one delta base instead of remapping every offset
9504e943Merge branch 'factory/fast-delete-checkout'
cfdcf8b8docs: delete-heavy switch and hard reset benchmarks
ff774456bench: daily rows for delete-heavy switch and hard reset
59aebc90reset --hard: one stat pass, no content reads for deletions
8fc6bf85switch: hash racily clean paths in parallel from the guard's scan stats
1dc4795drevwalk: list A...B as a symmetric difference in RevWalk::walk