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

71 changes landed on main

60 issue fixes · 11 other changes

v0.5.22 mostly fixes correctness gaps against system Git and cuts memory and time on large blobs, big worktrees and push, fetch and clone.

  • ›Large blobs stream through cat, status, add, restore and local clone instead of using multi-GB RSS (#2010, #1850)
  • ›Faster: blobless hard reset 139.5s to 1.0s (#2054), path-limited log 1.58s to 0.29s, clone checkout 4.16s to 2.91s on 100k files
  • ›Crash-safe history rewrites via a rewrite-apply journal; reset --hard clears merge state; failed checkout deletes roll back (#1942, #1965, #1968)
  • ›Git compat: merge drivers, ISO-8859-1 encoding, ident attribute, core.ignorecase, subtree-merge blame, SHA-256 repos, shallow depth, GIT_OBJECT_DIRECTORY
  • ›Push, fetch and server: remote hook output shown, upload-pack v2 server-option and object-info, git-lfs clone fixed, ref-delete race fixed
  • ›Clearer errors: typed lock-held, permission-denied paths, nothing-staged commits, first-run and Git-habit hints, HTML-200 URLs
$ git log 3de23d18..v0.5.22172 commits on GitHub ↗300+ files +12,331 −2,193
Highlightsmeasured by the reviewers, before → after
Blobless Cargo checkout takes 151 s vs Git's 0.80 s: one lazy fetch and pack per missing blob
200-file data fetches200× better████████████████████████ 200 fetches █ 1 fetches
Bounded branch --contains on 10k refs takes 1.5 s and 100 MB (Git: 150 ms / 31 MB)
json contains / 1 wall; system Git 34.95 ms154× better████████████████████████ 2,440 ms █ 15.9 ms
receive-pack unpack path (<100 objects) with a 600 MB blob: 66 s / 1.65 GB RSS (git: 15.6 s / 102 MB)
Exact repro peak RSS (origin/main → reviewed branch)97× better████████████████████████ 1,381,200 KiB █ 14,248 KiB
grit add/commit of a 1 GB file peaks at 2.6-3.0 GB RSS (git streams it: 5 MB)
128 MiB indexed stat-dirty add peak RSS: origin/main to branch34× better████████████████████████ 403,868 KiB █ 11,956 KiB
grit local-path clone loses the pack fast path when the source has one loose object: cargo 0.19s -> 2.3s, git/git 17.9s
Mixed bare local clone median wall time (debug binary, 20,003 packed + 4 loose objects)20× better████████████████████████ 0.289 seconds █ 0.014 seconds
issues fixed60
  • ›A one-file commit makes O(directories) ObjectStore::contains calls (2003 for 1000 dirs, 4 writes)#1890daed43be

    This adds a batch existence-query extension point to ObjectStore and changes cache-tree rebuilding to confirm reusable trees in batches. A one-file commit no longer probes every unchanged directory twice, while still rebuilding missing descendants and reporting missing blobs.

    Scalar contains calls, 1000-directory one-file commit: 2,003 calls → 1,003 calls (100% better)Single release reproducer commit: 23.5 ms → 11.1 ms (2.1× better)Grit release commit median, 20k files/1000 dirs/15 samples: 46.8 ms → 44.4 ms (5% better)
  • ›working-tree-encoding=ISO-8859-1: commit stores raw Latin-1 in the blob; clone/restore/switch fail#1963209d09df

    This change makes ISO-8859-1 working-tree encoding round-trip correctly: clean converts Latin-1 worktree bytes into UTF-8 blob bytes, while smudge converts representable UTF-8 back to Latin-1 and rejects unrepresentable text without data loss. It adds direct and system-Git compatibility coverage for clean, checkout, clone, aliases, and strict failure behavior.

  • ›grit switch exits 0 and says nothing when it cannot delete a file; stray file is left behind as untracked#19687f7b2b0b

    This change makes checkout deletion failures visible and transactional: per-file removals are staged for rollback, failures preserve HEAD/index/worktree consistency, and the staging payload is spooled to disk instead of retained in RAM. The current implementation is not safe to integrate because its cross-filesystem spool path changes symlinks into regular files during rollback.

    32 x 8 MiB deletion peak RSS on origin/main vs review branch: 40,316 KiB → 41,736 KiB (3% worse)32 x 8 MiB deletion elapsed time on origin/main vs review branch: 0.147 seconds → 0.145 seconds (1% better)32 x 8 MiB deletion peak RSS, system git vs review branch: 13,036 KiB → 41,736 KiB (69% worse)
  • ›grit push discards the remote's sideband output: hook stderr ("remote: ...") never reaches stdout/stderr or --json#1937a572bb7f

    Remote receive-pack hook and diagnostic text now survives push sideband demultiplexing. Embedders receive ordered lines through `grit-lib::PushOutcome`, while the CLI emits them to non-TTY/TTY stderr for human output and includes them in JSON and Markdown.

  • ›kill -9 during rebase/apply_rewrite leaves refs moved but worktree half-updated; grit commit then commits the revert#19425a87b98b

    Adds a durable `.git/grit/rewrite-apply` journal around history ref movement and worktree/index application. Interrupted operations can now finish or roll back safely, status exposes the state, commits and rebases recover or refuse unsafe progress, and legal newline-containing Git paths survive snapshot serialization.

  • ›grit clone of any git-lfs repo fails: smudge filter runs outside the new repo (exit 128 'Not in a Git repository')#1959fce8ce6e

    Repository handles now carry repository-scoped cwd and environment into both long-running filter-process drivers and one-shot clean/smudge commands. Clone checkout therefore launches git-lfs inside the new worktree, while linked worktrees and Windows path forms receive the correct repository context too.

  • ›receive-pack unpack path (<100 objects) with a 600 MB blob: 66 s / 1.65 GB RSS (git: 15.6 s / 102 MB)#19450657d38d

    Small receive-pack inputs now inflate large non-delta blobs directly into chunked loose-object writes instead of materializing the payload in memory. Strict dot-special validation walks in-memory structural objects and reloads only blobs actually named `.gitmodules` or `.gitattributes`; malformed zlib streams and temporary-file failures also terminate cleanly.

    Exact repro wall time (origin/main → reviewed branch): 15.3 seconds → 9.7 seconds (58% better)Exact repro peak RSS (origin/main → reviewed branch): 1,381,200 KiB → 14,248 KiB (97× better)Exact repro wall time (system git → reviewed branch): 16.5 seconds → 9.7 seconds (71% better)Exact repro peak RSS (system git → reviewed branch): 102,240 KiB → 14,248 KiB (7.2× better)
  • ›SHA-256 repos made by git: grit fsck/gc fail on the all-zero reflog id, grit clone fails with pack checksum mismatch#19627092a594

    This change makes Grit maintenance and cloning work with Git-created SHA-256 repositories. It excludes hash-width-independent null reflog IDs from reachability and synchronizes clone destinations to the advertised/source object format, reopening the post-fetch repository before checkout.

  • ›upload-pack v2 does not advertise server-option, wait-for-done, object-info: `git fetch --server-option` dies#1946881674fe

    Protocol v2 upload-pack now advertises and handles server options, wait-for-done, and object-info. The round-2 correction also makes object-info distinguish an absent object from repository/read failures, so corruption and I/O failures are no longer silently represented as missing objects.

  • ›Ref listing races with a concurrent ref delete: 'I/O error: No such file' kills upload-pack/receive-pack#194472aba557

    Loose-ref enumeration now skips an entry when it vanishes between readdir and metadata lookup, preventing unrelated concurrent ref deletion from aborting branch listing or server ref advertisement. A focused concurrent writer/lister unit test guards the behavior.

    Concurrent churn reproduction wall time (single noisy debug run): 8,043 ms → 8,583 ms (6% worse)
  • ›First-run dead ends: `switch -` hint says create branch '@{-1}'; log/show/reset/stash pop in a new repo give no hint#197195aa3c4b

    Fresh repositories now receive actionable, structured errors instead of leaked revision syntax: switch reports no previous branch, history commands explain an unborn branch, and empty stash pop reports no entries. Round 2 adds the missing public API snapshot entries, extends the fresh-repo regression to rebase, and documents the new error contracts.

  • ›`ident` attribute: grit status shows expanded $Id: ...$ as modified and commit stores the expansion in the blob#1964785be790

    Clean conversion now collapses expanded ident keywords before hashing or comparing worktree content. Ident-marked files also bypass the raw streaming identity path so status, diff, add, commit, and stash all see canonical repository bytes.

  • ›Ignore rules ignore core.ignorecase=true: Build/ and *.LOG don't match build/ and debug.log, grit add/commit stages them#19124ffbf64d

    This change makes ignore-rule matching honor `core.ignorecase` using ASCII folding and closes the round-2 safety gap for tracked paths whose on-disk casing differs from the index. Status, clean, staging, auto-stage commit, and ignore checks are covered against system Git, including the case where an ignored directory contains a case-variant tracked file.

    IgnoreMatcher check_path over 10k paths (Criterion median): 24.3 ms → 20.3 ms (20% better)30k tracked-file status, origin/main vs reviewed branch (30-run median): 15.5 ms → 19.8 ms (22% worse)30k tracked-file status, system Git vs reviewed branch (30-run median): 10.6 ms → 19.8 ms (46% worse)
  • ›commit.autoStage=false: 'nothing to commit — working tree clean' shown while files are modified/untracked#1969ef536ca3

    Index-only commits now distinguish a dirty but unstaged worktree from a genuinely clean tree. Library callers receive a typed error with unstaged/untracked counts, while the CLI emits stable `nothing_staged` or `nothing_to_commit` JSON kinds and actionable human guidance.

    Stable error kinds represented in error.v1 schema: 29 kinds → 31 kinds (7% better)
  • ›grit reset --hard after a conflicted merge/pick keeps MERGE_HEAD/CHERRY_PICK_HEAD; next commit records a bogus merge#1965d60aeb23

    A successful reset now ends merge, pick, and revert state so later commits cannot accidentally retain a second parent or replay metadata. When the interrupted merge used autostash, reset stores that commit in the stash reflog without applying it, matching system Git.

  • ›fetch --depth N on a shallow clone deepens by N each time (1->2->4); clone --depth 1 -b <tag> not shallow#1934e908a15b

    This change makes absolute shallow fetch depth stay absolute across repeated fetches, keeps relative deepen behavior distinct, and makes shallow clones by lightweight or annotated tag stop at the requested commit depth. It also completes protocol-v0/stateless Smart-HTTP shallow negotiation so grit clients and servers agree on shallow and unshallow boundaries.

  • ›grit blame blames the whole file on a subtree-merge commit; git follows the rename into the merged-in parent#19673e540f39

    Blame now retains every merge parent long enough to find files renamed on the merged-in side, then hands matching lines to that parent under its original path. This fixes subtree merges and the mixed case where one parent has the result path while another has the content under an older name.

  • ›Custom merge drivers (merge.<name>.driver + .gitattributes merge=<name>) are ignored by merge/pick/rebase#196699000f51

    This adds low-level merge-driver resolution and execution to the shared tree-merge engine. Configured .gitattributes drivers, merge.default, built-in union/binary behavior, placeholders, conflict blobs, and add/add mode conflicts now produce the same trees and index stages as system Git.

  • ›history apply_rewrite/start_rebase ignore injected RefStore: Err 'HEAD has no commit' after the ref already moved#18885f3faf12

    History rewrites now consistently read and update HEAD, branches, and reflogs through Repository's injected RefStore. Post-apply HEAD is computed before the ref transaction, preventing a late HEAD lookup from returning an error after refs have already moved; exec and undo use the same store-aware path.

    Focused injected-store outcomes correct (plan HEAD, apply/move, HEAD reflog, undo): 2 of 4 → 4 of 4 (2.0× better)history-drop-100 median on 5,000-commit/10,000-file fixture: 111.8 ms → 111.5 ms (0% better)history-drop-100 peak RSS: 29,011,968 bytes → 29,188,096 bytes (1% worse)
  • ›GIT_OBJECT_DIRECTORY is ignored for object reads and writes (GIT_ALTERNATE_OBJECT_DIRECTORIES works)#19077677a4c4

    Repository opening now makes GIT_OBJECT_DIRECTORY the actual primary ODB for reads and writes, including relative paths and explicit empty-value failure. Fetch, push, pull, and bundle flows retain and use the caller's Repository/Odb instead of reopening .git/objects, while object-store consumers consistently ask the Odb for its directory.

    Local fetch median, 5,000 commits + 33 ahead: 9.5 ms → 8.9 ms (6% better)
  • ›grit commit --staged / commit.autoStage=false with only unstaged edits says 'nothing to commit — working tree clean'#19766d962c98

    Commit failures now distinguish a truly clean work tree from a staged-only commit attempt that leaves unstaged or untracked work. Human, JSON, and Markdown-facing error data carry accurate context and hints, including path-scoped commits that must not treat unrelated staged files as unstaged.

  • ›clone/fetch of a typo'd URL whose host returns HTML 200: 'I/O error: invalid digit found in string'#19883a2dd709

    HTTP smart-discovery responses that look like HTML are now classified as a typed NotGitServer error instead of leaking a pkt-line parsing failure. The CLI renders a clear status/content-type message and reuses the existing check-the-URL next step.

  • ›status footer says `grit pull to update from main` for a local-only repo; pull fails with unknown remote#19901b88046a

    Status now distinguishes a configured upstream from a local or unrelated comparison ref, suggesting `grit pull` only when pull would integrate the shown ref and `grit merge <ref>` otherwise. Fast-forward output names the current branch that actually moved, while retaining the merge source and adding an optional `updated_branch` JSON field; round two commits the corresponding snapshot.

  • ›commit/gc fail with bare 'I/O error: Permission denied (os error 13)': no path, no hint (root-owned .git files)#1970a14bc89c

    Adds path-aware, typed errors for writes under repository metadata so commit and gc can identify the failing object, ref, reflog, index, or pack path and the CLI can return structured JSON guidance. The bare-gc and direct files-ref-store cases work, but detached linked-worktree commit still loses the path before reaching the newly fixed ref transaction.

  • ›Git-habit commands give misleading tips: checkout -b/-- <file>, reset HEAD <file>, add -A#1989b6cb28ce

    Adds a CLI-only migration-hint layer that recognizes common Git spellings and points users to the corresponding grit command without exposing clap’s misleading pass-as-value tip. It also preserves the alias the user typed in usage text and distinguishes existing checkout paths from plausible branch names.

    Runtime hint scenarios producing the intended guidance: 2 scenarios out of 10 → 10 scenarios out of 10 (5.0× better)
  • ›grit local-path clone loses the pack fast path when the source has one loose object: cargo 0.19s -> 2.3s, git/git 17.9s#197393e2e85c

    Local initial clones now install existing packfiles and loose objects directly, then compute transfer totals from pack indexes plus only the reachable loose tip. This keeps mixed packed/loose repositories on the local fast path while preserving the slower complete transfer path for alternates and other unsupported shapes.

    Mixed bare local clone median wall time (debug binary, 20,003 packed + 4 loose objects): 0.289 seconds → 0.014 seconds (20× better)Mixed bare local clone median peak RSS: 45,208 KB → 37,152 KB (22% better)Mixed non-bare local clone median wall time (20,000-file checkout included): 1.3 seconds → 0.883 seconds (46% better)Mixed non-bare local clone median peak RSS: 53,964 KB → 52,196 KB (3% better)
  • ›Non-executable hook warning tells users to run `grit config set advice.ignoredHook false`, which grit rejects#19923a770f9d

    The ignored-hook warning now gives two commands users can actually paste: a correctly quoted `chmod +x` command to enable the hook, and the accepted `grit config advice.ignoredHook false` command to silence the warning. The hook path is carried as typed library diagnostic data and rendered only by the CLI.

  • ›grit push of ONE new commit over ssh/http burns CPU like a full-history walk: git/git 1.9s/242MB (git 0.04s/44MB)#1975741ad80a

    Routes push pack construction through a send-pack-specific boundary mode and preserves divergent-tip correctness by painting advertised-have ancestry through the repository commit cache. It also updates the public API snapshot and adds Git-backed pack tests, but the round-2 ancestry paint traverses the complete remote history before building each push pack.

    git/git one-commit SSH push, 2-run mean: 0.709 seconds → 0.785 seconds (10% worse)reviewed grit versus system git one-commit SSH push: 0.051 seconds → 0.785 seconds (94% worse)
  • ›grit push <tag> reports 'pushed main → origin refs/tags/<tag>' and json branch="main"#2004f0b57c3e

    Single-tag pushes now derive their summary name from `refs/tags/<name>` instead of falling back to the checked-out branch. Human and Markdown output show the tag, while JSON omits the legacy `branch` field for tag and other explicit refspec pushes.

  • ›Windows: first grit status after clone/switch takes 3-13 s on 3k files (git 0.2 s); later runs 0.17 s#1914e84b6156

    Racily clean worktree files are now content-checked through an unconditionally parallel worker path when multiple workers and files are available. Matching checks are recorded on the index, and a persisted status refresh advances the index timestamp using the existing guarded helper.

    3,000-file status-after-checkout median: 10.4 ms → 10.6 ms (2% worse)100-file status-after-checkout median: 3.3 ms → 3.2 ms (1% better)3,000-file status-after-checkout peak RSS: 19,038,208 bytes → 19,931,136 bytes (4% worse)
  • ›Follow-up: Fix #1968: grit switch exits 0 and says nothing when it cannot delete a file; stray file is left behind as untracked#196805b806d7

    Checkout deletions are now staged in a bounded temporary spool so a failed switch can restore already-removed tracked files. Cross-device moves preserve symlinks as symlinks, and the corrected regression proves rollback reaches the later permission failure rather than exiting early on a dangling link.

  • ›grit rm / grit mv on a split-index repo leave the old entry in the index; later grit status shows tracked files as A.#200356b895ca

    Locked rm/mv index updates now retain split-index serialization, including delete and replacement bitmaps, while preserving the existing flush-then-finish lock lifecycle. The round-two patch restores a silently skipped compatibility test and ensures an uncommitted flush changes permissions only on index.lock.

  • ›macOS: clone of case-colliding paths gives no warning, then auto-staging `grit commit` silently rewrites File.txt#199171efc5db

    Adds typed reporting for case-insensitive checkout collisions, corrects clone file/byte summaries to reflect actual worktree slots, and prevents default commit auto-stage from rewriting collision-loser index entries. Human clone output warns on stderr while JSON and Markdown expose stable collision paths.

    20k-entry collision-path probe: 61 ms → 60.5 ms (1% better)
  • ›grit-lib: ResetOptions/ApplyOptions/CloneReflog/ReplayRequest take identity as String, written unvalidated to reflog#1972c21e5648

    Reflog-writing porcelain APIs now accept validated Signature values instead of caller-formatted identity strings. Backend-independent transaction validation rejects newline/control injection even when callers use RefStore::prepare directly, and reset preserves its best-effort identity behavior for unconfigured users.

  • ›Follow-up: Fix #1970: commit/gc fail with bare 'I/O error: Permission denied (os error 13)': no path, no hint (root-owned .git files)#197010a30615

    This change makes repository-metadata permission failures identify the affected path instead of returning a bare OS error, including commits from detached linked worktrees. It also normalizes displayed repository paths across Windows, normal worktrees, linked worktrees, and bare repositories.

  • ›show/log --patch-text print the human side-by-side view, not a git-apply patch (diff --patch-text is fine)#2005ea7f4942

    This change streams apply-ready unified patches for `grit show --patch-text` and `grit log --patch-text`, including binary bodies, instead of the human side-by-side renderer. The round-2 update forwards resolved pathspecs, supports first-parent merge patches, and places show decorations before streamed commit output, but source-side rename filtering remains incorrect.

  • ›grit add/commit of a 1 GB file peaks at 2.6-3.0 GB RSS (git streams it: 5 MB)#1850c0b0c50b

    Large tracked binary files can now be re-hashed as raw streams even when core.autocrlf is enabled and an index entry exists. The round-two patch also restores metadata-aware symlink handling in the serial index/worktree diff path and adds direct regressions for both cases.

    128 MiB indexed stat-dirty add peak RSS: origin/main to branch: 403,868 KiB → 11,956 KiB (34× better)128 MiB indexed stat-dirty add elapsed: origin/main to branch: 0.941 seconds → 3.0 seconds (68% worse)128 MiB indexed stat-dirty add peak RSS: system Git to branch: 135,884 KiB → 11,956 KiB (11× better)128 MiB indexed stat-dirty add elapsed: system Git to branch: 0.623 seconds → 3.0 seconds (79% worse)
  • ›grit commit -- <path> refuses an ignored path that is already tracked or force-added (git commit <path> works)#20062a0f003c

    Path-scoped commits now accept ignored files that are already tracked or force-added without accidentally committing staged content outside the selected pathspec. The follow-up fixes preserve parent-tree content for negative pathspecs and support ignored directory pathspecs while avoiding duplicate ignore validation.

  • ›Windows: reset --hard / stash pop / merge rewriting 3-5k modified files is 7-11x slower than git (switch is not)#20098a6b557b

    This change routes high-volume hard reset, stash apply, and clean merge materialization through the optimized checkout/index paths. The round-two corrections ensure stash reconciliation owns the final index write and hard reset preserves the index timestamp invariant needed for reliable racy-file detection.

  • ›grit commit runs gc --auto in the foreground while printing 'in the background': 8k-file commit 37 s (git 3.3 s)#2008c190a4ff

    Automatic gc now requests Windows job breakaway and treats a denied breakaway as a failed background launch. It falls back to synchronous maintenance with truthful output, eliminating the prior retry that could remain trapped in a restrictive job while claiming to run in the background.

    8k-file release commit latency, origin/main to branch (Linux single run): 171 ms → 219 ms (22% worse)8k-file release commit latency, system Git to branch grit (Linux single run): 65 ms → 219 ms (70% worse)
  • ›Held index.lock surfaces as Error::Io(Custom) with prose; no typed lock error or JSON kind#1797ca872571

    This change turns held index and loose-ref lock files into a typed library error with optional PID/liveness information, then exposes that data consistently through Grit's human, Markdown, and JSON error outputs. The round-2 amendment ensures this remains true when the typed library error is nested under another error and anyhow context.

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

    Local clones now hard-link eligible loose objects before packing the remaining objects, avoiding large in-memory repacks. Human `grit cat` streams blobs of at least 1 MiB and resolves replacement objects through documented library APIs whose signatures are committed in the public API snapshot.

  • ›Follow-up: Fix #1817: grit merge accepts only branch names: tags, commit ids, ORIG_HEAD and rev expressions fail with 'no branch named'#1817abdf22dd

    Merge source resolution now accepts branches, tags, commit IDs, ORIG_HEAD, and revision expressions through a typed grit-lib API. Its range guard correctly handles legal ref names containing bare braces, preventing three-dot input from being resolved to a merge base and accidentally committed.

  • ›Follow-up: Fix #1991: macOS: clone of case-colliding paths gives no warning, then auto-staging `grit commit` silently rewrites File.txt#19917ac4cac4

    Auto-staging keeps unrelated case-collision groups separate even when their 32-bit hashes collide. The branch also reuses staging collision metadata, restores direct path lookup, and makes the dentry regression select the correct fixture on case-insensitive storage.

  • ›Follow-up: Fix #1975: grit push of ONE new commit over ssh/http burns CPU like a full-history walk: git/git 1.9s/242MB (git 0.04s/44MB)#1975818de800

    Send-pack now ends revision painting as soon as no queued commit remains reachable only from the WANT side, instead of draining advertised HAVE history to its roots. A test-only ODB-read counter locks in the bounded walk on deep history.

    82,563-commit git.git warm one-commit fake-SSH push median: 1.5 seconds → 0.139 seconds (11× better)82,563-commit git.git warm one-commit fake-SSH push median max RSS: 102,348 KiB → 86,444 KiB (18% better)
  • ›1 GB blob: status/add peak 3.2 GB, cat 4.6 GB, restore 3.2 GB, local clone 5.6 GB RSS (git: 10 MB / 10 MB / 1 GB)#201034c10122

    Streams large object payloads through cat, status/add hashing, restaging, pack serialization, restore-related reads, and local clone paths instead of retaining multiple whole-blob copies. Local clones preferentially hard-link loose objects, while JSON deliberately omits large blob bytes and exposes that choice in the schema.

    64 MiB status peak RSS, origin/main→branch: 16.8 MiB → 16.3 MiB (3% better)64 MiB add peak RSS, origin/main→branch: 78.6 MiB → 15.4 MiB (5.1× better)64 MiB cat peak RSS, origin/main→branch: 291.0 MiB → 14.9 MiB (20× better)64 MiB restore peak RSS, origin/main→branch: 16.2 MiB → 15.9 MiB (1% better)
  • ›Follow-up: Fix #2005: show/log --patch-text print the human side-by-side view, not a git-apply patch (diff --patch-text is fine)#2005f2354712

    Branch and annotated-tag headers now stay ahead of streamed patches when output is piped or redirected. Log patch pages retain their continuation hint, while existing path filtering and merge behavior stay covered.

  • ›cargo doc -p grit-lib --no-deps fails on three invalid public rustdoc links#2031b5b02e21

    Embedding developers can build and browse grit-lib's public API docs again. The patch replaces two unresolved links with public item paths and describes relative deepen without linking to a private helper.

  • ›grit-lib reset_to_commit returns InvalidSignature after moving HEAD and discarding worktree edits#2033a61c6cf7

    Rejects malformed caller-supplied reset identities before HEAD, the index, worktree, reflogs, or operation state can change. Preview and dry-run share the check, including current-HEAD targets and disabled reflog updates.

  • ›No-op local fetch with 3,000 branches and 2,000 tags takes 262ms vs git 51ms (5x)#20375a715a52

    Local fetch stops reopening ref/config state for thousands of tracking refs and tags. It skips preparing object-copy exclusions on an up-to-date fetch while preserving pruning, ref classification, FETCH_HEAD, and remote HEAD maintenance.

    One-file, 5,000-ref fixture, default (system Git 41.00 ms): 231.5 ms → 38.0 ms (6.1× better)One-file, 5,000-ref fixture, default peak RSS: 16,956 KiB → 15,704 KiB (8% better)One-file, 5,000-ref fixture, no-tags (system Git 36.58 ms): 219 ms → 26.8 ms (8.2× better)One-file, 5,000-ref fixture, no-tags peak RSS: 16,552 KiB → 15,476 KiB (7% better)
  • ›Clean 20k-file content merge peaks at 98 MB RSS vs Git's 35 MB despite fixed linear runtime#2036c578daa5

    Wide content merges retain fewer duplicate paths, index records, parsed trees and checkout plans. Independent quiet measurements show 25–35% lower peak RSS and 1.7–3.1% faster median merges; results remain compatible with system Git.

    10k loose merge (Git 2148 ms): 1,826 ms → 1,770 ms (3% better)10k loose peak RSS (Git 19.46 MiB): 55.0 MiB → 41.1 MiB (34% better)20k loose merge (Git 4308.1 ms): 3,609 ms → 3,548 ms (2% better)20k loose peak RSS (Git 33.87 MiB): 91.1 MiB → 59.1 MiB (54% better)
  • ›grit panics on any non-UTF-8 path argument (status, blame, add): exit 101 in std::env::args#2044cf90e3d9

    Keeps native argument bytes intact until parsing, so unsupported textual operands produce usage errors instead of panics. Adds an early repository-selection guard that prevents raw-byte directory names from redirecting commands to a similarly named Unicode repository.

    Release --version mean, origin/main → branch: 1.7 ms → 1.6 ms (4% better)Release clean 10k-file status mean, origin/main → branch: 13.9 ms → 13.2 ms (5% better)
  • ›Bounded branch --contains on 10k refs takes 1.5 s and 100 MB (Git: 150 ms / 31 MB)#2035e10563aa

    Bounded branch containment now preserves matches and truncation when grafts or replacement refs create ancestry cycles. Ordinary histories retain the fast memoized path; rewritten histories use the existing cycle-aware batch walk and ignore stale commit-graphs.

    json contains / 1 wall; system Git 34.95 ms: 2,440 ms → 15.9 ms (154× better)json contains / 100 wall; system Git 208.33 ms: 2,438 ms → 36.8 ms (66× better)json contains / 1000 wall; system Git 34898.49 ms: 2,415 ms → 238.6 ms (10× better)json contains / unlimited wall (3-sample repeat); system Git 35047.5 ms: 2,463 ms → 2,675 ms (8% worse)
  • ›grit switch refuses the first checkout after clone --no-checkout as uncommitted changes#205271604e01

    The first switch after a no-checkout clone now writes the complete requested tree and creates the index, including when selecting the current HEAD or a same-tree branch. It keeps the real previous HEAD for hooks and reflogs, protects colliding files, and retains staged deletions represented by an existing empty index.

    Initialized switch median: origin/main to branch: 16.8 ms → 16.8 ms (0% worse)Initialized switch median peak RSS: origin/main to branch: 19,802 KiB → 20,136 KiB (2% worse)
  • ›Local fetch --unshallow omits reachable historical tags until a second fetch#205363beba26

    Local unshallow now creates historical release tags during the first fetch, including annotated tag chains. It computes the requested history before choosing automatic tags, then copies missing tag objects and retains the existing availability safeguard.

    Single unshallow; baseline incomplete, system Git 219.602 ms: 209.0 ms → 480.0 ms (56% worse)
  • ›grit-lib blame_file cannot blame non-UTF-8 filenames returned by status (reversible path encoding is not decoded)#2045dbdf37d2

    Blame accepts reversible paths reported by status and preserves exact filename bytes in attribution. The review follow-up also prevents ./grit+pathhex:6162 and equivalent cwd-relative operands from silently selecting the file ab.

    UTF-8 full blame median; system Git 92.501 ms: 107.6 ms → 100.1 ms (7% better)UTF-8 lines 1–200 median; system Git 82.414 ms: 75.0 ms → 66.9 ms (12% better)
  • ›grit patch-id hashes whole-file context and disagrees with git --stable on a ten-line file#2055482a34e4

    Patch IDs now include nearby unchanged context while excluding the rest of the file. The exact ten-line example produces Git's expected 5ab484ff6e8f56d89403fae49e2063b0712ed58e through both the library-backed command and the normal rendered patch.

    Sparse-file patch-ID median wall time (system Git: 67.15 ms): 193.5 ms → 156.5 ms (24% better)Sparse-file patch-ID median peak RSS (system Git: 64,380 KiB): 103,972 KiB → 103,220 KiB (1% better)
  • ›Blobless Cargo checkout takes 151 s vs Git's 0.80 s: one lazy fetch and pack per missing blob#2054f0e7cddd

    Hard reset now gathers the missing content it will need and downloads it together before materializing files. My pinned Cargo reproduction falls from 139.5 seconds to 1.00 second, with one data fetch instead of 2,022.

    200-file blobless reset, median of 3 fresh clients (Git 47.556 ms): 2,262 ms → 60.4 ms (37× better)200-file blobless reset peak RSS (Git 5,596 KiB): 23,928 KiB → 17,320 KiB (38% better)200-file data fetches: 200 fetches → 1 fetches (200× better)200-file resulting packs: 201 packs → 2 packs (101× better)
  • ›blame ignores final-newline changes and attributes the last line to the wrong commit#206399332d04

    Blame now compares complete borrowed line records, so adding or removing the final LF correctly assigns the last line to the modifying commit. Output content and line numbers stay unchanged, and explicit whitespace ignoring still passes through newline-only changes.

    Exact Git matches across 500 extra deterministic histories: 362 histories → 498 histories (38% better)Packed deep-2000 wall time; system Git 10.071 ms: 9.4 ms → 9.4 ms (0% better)
  • ›Reftable worktree add/switch write a loose HEAD that disagrees with system Git#2062ae092662

    Git can now read the branch and commit selected by grit when adding or switching a reftable worktree. Symbolic and detached HEAD values are committed to the worktree's own reftable stack; loose HEAD files remain discovery placeholders, and compaction no longer imports stale loose state.

    Files detached add: origin/main → branch: 96.5 ms → 103.3 ms (7% worse)
other changes11
  • ›Quality #34: Library API qualityf6a3780e

    Pack index checksum mismatches and unresolved pack deltas now have structured library error variants instead of requiring callers to parse text. Fsck uses the typed checksum error, thin-pack detection uses the typed unresolved-delta error, and optional index loading no longer carries an unreachable exact-string exception.

    fsck-medium grit median: 155.4 ms → 146.5 ms (6% better)fsck-medium grit mean: 157.3 ms → 149.1 ms (5% better)
  • ›Quality #35: Move logic into grit-lib6388d987

    Status models now directly report whether the worktree is clean, group conflicts by typed kind, and return typed next-step guidance. The CLI’s human and Markdown renderers consume those results instead of independently re-deriving the same policy, with existing output preserved.

    grit status dirty 1k median: 3.0 ms → 3.0 ms (2% worse)system git status dirty 1k control median: 2.5 ms → 2.5 ms (0% worse)grit status clean 1k median: 2.8 ms → 3.0 ms (5% worse)system git status clean 1k control median: 2.2 ms → 2.2 ms (1% better)
  • ›Quality #36: Dead code and dependencies000f9968

    This removes dead library and CLI surfaces plus unused test helpers, reducing the branch by 376 net lines and eliminating three empty integration-test binaries. It also turns two previously orphaned unit-test functions back into real tests and keeps bundle failure code 128 while mapping it to the correct typed status.

    bundle create median: 238.3 ms → 243.5 ms (2% worse)bundle verify median: 2.6 ms → 2.4 ms (9% better)bundle create peak RSS: 57,761,792 bytes → 57,372,672 bytes (1% better)bundle verify peak RSS: 9,703,424 bytes → 9,289,728 bytes (4% better)
  • ›Quality #38: Deduplicate12b52a55

    Both target-comparison entry points now share one library implementation. The required API still returns its existing error when discovery finds no target; the optional API still returns None.

    Production comparison implementations: 2 implementations → 1 implementations (2.0× better)grit shortlog/ahead@git-git median: 2.6 ms → 2.6 ms (1% better)
  • ›Speed: log/path@git-git74adad7a

    Path-limited history scans tree data directly for requested paths, reuses packed delta bases, and stops merge comparisons after finding the parent it follows. My target run fell from 1.58 seconds to 0.29 seconds, with 12.3% less peak memory; sparse history also keeps merges selected by Git.

    log/path@git-git wall (stock Git 249.44 ms): 1,580 ms → 289.7 ms (5.5× better)log/path@git-git peak RSS (stock Git 250820 KiB): 309,468 KiB → 271,308 KiB (14% better)log/limit-path@git-git wall (stock Git 15.78 ms): 34.7 ms → 12.9 ms (2.7× better)log/limit-path@git-git peak RSS (stock Git 30792 KiB): 39,952 KiB → 35,936 KiB (11% better)
  • ›Quality #39: Simplifyf9bf342b

    Patch parsing shares file initialization and the binary/hunk body loop across Git and traditional headers. Public signatures and parsed results stay unchanged; production code shrinks by 26 lines, and the benchmark now measures a valid patch and checks success.

    Criterion parse_patch_small, corrected fixture: 374.3 ns → 343.7 ns (9% better)add/patch-file@synth-10k; Git controls 5.3162→5.4126 ms: 7.5 ms → 7.2 ms (4% better)
  • ›Quality #40: Library API qualityf2a97fc8

    Rust callers can now parse reflog references and newest-first entry selectors through a documented library API with distinct syntax and integer errors. The CLI calls that API and preserves its existing human, JSON, and Markdown error output.

  • ›Speed: status/clean@synth-10k3511e3d3

    Dense tracked-directory scans reuse an open parent and a filename buffer on 64-bit Linux; status also reuses its existing configuration snapshot. Independent clean 10k status improved 14.8%, from 19.105 to 16.276 ms, beating system Git's 17.882 ms; RSS changed little.

    status/clean@synth-10k (system Git 17.882 ms): 19.1 ms median → 16.3 ms median (17% better)status/renamed@synth-10k (system Git 17.053 ms): 19.1 ms median → 16.3 ms median (17% better)status/dirty@synth-10k (system Git 17.262 ms): 19.5 ms median → 16.5 ms median (18% better)status/clean@small (system Git 2.115 ms): 4.4 ms median → 4.2 ms median (5% better)
  • ›Speed: ignore/check-1000@synth-10kfa6a9fd5

    Ignore queries now reuse the worktree root already resolved by Repository instead of resolving it again for every path. The existing fallback still handles alternate filesystem spellings, and the added tests protect boundaries, missing paths, trailing slashes, Unicode, symlinks, and bare-repository errors.

    Release direct check-1000@synth-10k (system git 3.934 ms): 12.0 ms → 9.0 ms (34% better)Release siblings-1000 (system git 1.727 ms): 9.4 ms → 6.4 ms (46% better)Release deep-1000 (system git 2.230 ms): 16.3 ms → 13.0 ms (26% better)Release tracked-1000 (system git 11.823 ms): 8.7 ms → 5.8 ms (51% better)
  • ›Perf regression 2026-10-25: clone/checkout@synth-100k, merge/clean@synth-10k, pick/one@synth-100k +35d557d22

    Restores checkout parallelism while retaining cancellation and bounded blob preparation. On this VM, all six reported regression scenarios improve by 11–35%; clone checkout drops from 4.16 to 2.91 seconds against system Git at 5.07 seconds. Public APIs, CLI outputs, repository formats and wire behavior are unchanged.

    worktree/add@synth-10k wall time (12 runs/tool; Git 236.69 ms): 299.6 ms → 194.2 ms (54% better)merge/clean@synth-10k wall time (12 runs/tool; Git 133.69 ms): 115.9 ms → 83.1 ms (39% better)pick/one@synth-10k wall time (12 runs/tool; Git 178.82 ms): 120.7 ms → 89.6 ms (35% better)revert/one@synth-10k wall time (12 runs/tool; Git 178.87 ms): 122.1 ms → 90.2 ms (35% better)
  • ›Quality #41: Move logic into grit-lib8a358bf6

    Embedding callers can now ask Repository::revision_label for branch or tag metadata, including raw annotations, without depending on the CLI. Show delegates that work to the library and preserves its human, JSON, and Markdown output.

    show/head@big-blobs: grit median wall time: 2.6 ms → 2.2 ms (15% better)show/head@big-blobs: system git median wall time: 1.8 ms → 1.7 ms (2% better)show/head@git-git: grit median wall time: 2.8 ms → 2.4 ms (17% better)
$ git log --oneline 3de23d18..v0.5.22compare on GitHub ↗
ae092662Store reftable worktree HEAD in its owning stack
99332d04Respect final newline changes in blame attribution
f0e7cdddBatch promised blobs before hard-reset materialization
482a34e4Hash patch IDs from normal three-line-context hunks
8a358bf6Move revision label classification into grit-lib
dbdf37d2Escape literal blame paths after normalization
0eb2c5ecDecode reversible paths throughout blame
5d557d22docs: record checkout performance and validation results
dfc83045checkout: scope small-object caching to bulk reads
46a42653odb: check cached object size before copying bounded reads
16a7b5cacheckout: bound streamed writes and cover distinct blob buffers
c857c65ccheckout: bound prepared contents and preserve worker thresholds
f1146a3cdocs: describe how checkout chunks parallel writes for cancellation
083b8f16odb: reuse small loose reads in read_unless_larger
7c95f022checkout: judge the parallel threshold once and grow cancellation chunks
63beba26Follow historical tags during local shallow fetches
71604e01Materialize the first switch after a no-checkout clone
fa6a9fd5Merge branch 'factory/ignore-batch'
48c5fd81Record ignore normalization benchmarks and memory limits
9fc6d4b7Avoid resolving the worktree root for each ignore query
3511e3d3Avoid repeated path and config lookups in worktree scans
f2a97fc8Move typed reflog selector parsing into the library
e10563aaRecord bounded containment review validation
75d9d9e9Keep rewritten ancestry on the batch containment path
6e3a6624Bound branch containment queries by matching rows
cf90e3d9Reject unsupported repository paths before discovery
787a30b6Preserve native OS arguments through CLI parsing
c578daa5Reduce memory retained by wide content merges
5a715a52Speed up local fetches with many refs
f9bf342bUnify patch parsing across diff header styles
a61c6cf7Validate reset signatures before mutating repository state
74adad7aRecord path history performance and validation results
8fbd1b4aPreserve commit object reuse when filtering history
2c124bf7Avoid duplicate sparse path filtering and cover packed history
34f22f3drevwalk: stop comparing merge parents at the first TREESAME one
99d342fcodb: keep delta bases cached when reading through the files primary
e75ed52brevwalk: scan raw trees in the dense path-limited walk
3a903d69odb: read_into variant that keeps delta bases cached
b5b02e21Fix public library documentation links
12b52a55Deduplicate target branch comparisons
f2354712Preserve show and log patch output order
34c10122Reuse shared display streaming and cover structured-output memory
5867ea14Bound loose clone statistics memory and validate large-blob streaming
92ca504bAddress review: cat replace refs, schema, fmt
af8594f2Fixes #2010: stream large blobs without multi-GB RSS spikes
818de800Cover incremental SSH push boundaries and record benchmarks
f6c528bfFix send-pack pack enumeration stopping at the want frontier (#1975)
7ac4cac4Merge branch 'factory/ignorecase-collision-4599'
abdf22ddrev-parse: share selector rules between range scanners
de16d136rev-parse: open range-guard selectors only at ^{ and @{
f23d430dRemove obsolete per-path collision-group helper
38297c77Fix case-insensitive filesystem probe in dentry test
66f9e8a1Make add collision checks linear and restore direct-path lookup
1ca35cceFix ignorecase collision group ids: use enumerate, not memihash
51fd34ddFixes #1849: record bounded-memory materialization benchmarks
ebc97a8dFixes #1849: bound memory while counting loose clone objects
ce8c8a02Update grit-lib public-api snapshot for cat streaming helpers
1d6dd400fmt: clone transfer stats test helper
e4be31b7Address review: warm clone RSS test, cat replace-ref streaming
d61606fbFixes #1849: stream large blobs on local clone and grit cat
ca872571Cover lock holder diagnostics and staging recovery
586b5770cli: report held locks as kind lock_held with the lock path
d3c99b17lib: one typed Error::LockHeld for held index and ref locks
c190a4ffDetach Windows automatic GC from the triggering job
a1f4e216Merge branch 'factory-agent-workflow'
a7b6b1daAdd the factory-agent workflow: Codex agents on Actions runners
8a6b557bMerge branch 'factory/fix-2009-reset-hard-perf'
2a0f003cMerge branch 'factory/commit-ignored-path-2006'
c0b0c50bMerge branch 'factory/big-file-stream-0158'
219a2f37Fix Bugbot: defer stash checkout index write; advance index mtime on hard reset
003c4119Fixes #2009: align hard reset, merge, and stash pop with fast checkout paths
ea7f4942Merge branch 'factory/show-log-patch-text-3cae'
10a30615Fix #1970: git-dir path I/O errors, including linked-worktree commit
c21e5648Rebase onto main: Signature in rewrite journal, reset autostash, and examples
c8483480Validate reflog metadata in RefTransaction::validate for direct prepare
97d7a70aAddress review: validate ReflogUpdate on ref transactions, public-api snapshot, test fixes
a3bbf4c4Fixes #1972: use Signature for reflog identity in reset, history, clone, and replay
71efc5dbAddress review: stderr warning, schema, public API, collision fixes
a610d201Fixes #1991: detect ignorecase checkout collisions and protect auto-stage
56b895caAddress Bugbot: restore split-index test and fix shared perm on flush
473099b4Fix split-index rm/mv leaving stale shared-index entries
bebfaa54Fix path-scoped commit for force-added ignored paths
d35880afReview: stream indexed binary under autocrlf; fix symlink diff path
a93e2c88Fixes #1850: stream big binary blobs when autocrlf is on
000f9968Merge branch 'factory/dead-test-helpers'
7ad085a7Fix rename pathspec matching for log/show patch-text
c9d84fa5Address review: pathspecs, merge first-parent, show ordering
caf32514Fix show and log --patch-text for git apply
05b806d7checkout: spool delete rollback with cross-device symlink restore
acffb1dfDelete dead integration-test helpers and make test targets warning-free
9b6ad46fClean up grit-lib unit tests: run two orphaned tests, drop dead bindings
55b1da55Remove the inert force_split_index option and unused IndexHandle methods
95a671b6Compile test-only helpers only under cfg(test) and drop unused CLI code
e84b6156Fixes #1914: parallel racy status refresh and index mtime after persist
f0b57c3eFix push tag summary and omit branch in JSON for refspecs
741ad80aMerge branch 'factory/push-pack-tips-boundary'
6388d987Merge branch 'factory/status-guidance-lib'
69d7a1faFixes #1975: send-pack ^have rev walk for pack-objects
f6a3780eindex: drop the unreachable "file too short" match in optional load
dd9a4af3error: typed pack-index checksum and unresolved-delta errors
4eaac7efstatus: render guidance and conflict groups from the library model
3a770f9dFix non-executable hook warning config hint
0da8707bstatus: compute next-step guidance, cleanliness and conflict groups in the library
93e2e85cFix local clone pack-link fast path when source has loose objects
b6cb28ceMerge branch 'factory/git-habit-hints-1989'
a14bc89cMerge branch 'factory/git-dir-path-io-1970'
3a2dd709Merge branch 'factory/not-git-server-html-200'
1b88046aMerge branch 'factory/status-merge-hint-4520'
6d962c98Fixes #1976: path-scoped commit uses repository index for empty commit errors
b83f2d48Fixes #1988: reject HTML 200 HTTP discovery as NotGitServer
d674565cFixes #1970: name .git paths on git-dir I/O errors
7677a4c4repo: place the primary object store at GIT_OBJECT_DIRECTORY
ea78d429transport: fetch and push through the caller's repository
efc6df73odb: take the object directory from the repository's Odb
cc9147b0Address review: narrow checkout restore heuristic and argv scan
0c619a5eFix misleading tips for Git-habit checkout, reset, add, and init argv
223a15e9Refresh merge_ff CLI snapshot for fast-forward wording
ea5aa5b2Fix status integration hints and merge fast-forward wording
5f3faf12history: read and move HEAD and refs through the repository's ref store
99000f51Merge branch 'factory/merge-drivers-1966'
3e540f39Merge branch 'factory/blame-subtree-merge-0183'
e908a15bMerge branch 'factory/fix-shallow-depth-1934'
d60aeb23Merge branch 'factory/reset-clears-merge-state'
ef536ca3Merge branch 'factory/commit-nothing-staged-4486'
4ffbf64dMerge branch 'factory/ignore-case-rules'
785be790Merge branch 'factory/ident-collapse-af01'
95aa3c4bUpdate public API snapshot and document first-run errors
de6a8acaFix first-run dead ends for switch, log, and stash
72aba557Merge branch 'factory/ref-list-race-1944'
881674feMerge branch 'factory/upload-pack-v2-caps-4442'
7092a594Fixes #1962: SHA-256 fsck/gc reflog null OID and clone pack checksum
0657d38dMerge branch 'factory/receive-pack-stream-unpack-4443'
fce8ce6eMerge branch 'factory/fix-clone-lfs-filter-cwd'
6ec63d8astatus, clean, add: case-variant tracked paths are not ignored under core.ignorecase
b31ccbb3ignore: specialize rule matching on case folding and drop per-rule Vec
6992791aignore: honor core.ignorecase when matching exclude patterns
5a87b98bMerge branch 'factory/crash-safe-apply-1942'
a572bb7fMerge branch 'factory/push-sideband-1937'
7f7b2b0bMerge branch 'factory/issue-1968-switch-delete-perm'
093347b1Refresh public-api snapshot after rebase onto main
ca895cd0Fixes #1965: clear merge and replay state on reset
88e4e5edEncode rewrite-apply snapshot paths as path-hex for newline filenames
80865ba7Fix rewrite-apply journal path encoding, empty files, and recovery gaps
6f1dbc4eRename markdown helper to satisfy CLI transport hygiene
2e2ad160Align push remote_messages schema with omitted-empty JSON
ba08808eAddress review: regenerate push schema and Bugbot fixes
5a58c82eKeep all merge parents before per-parent rename blame
a91bcd8cFix filter subprocess cwd during clone for git-lfs
85f71b78Fixes #1945: stream large blob unpack in receive-pack
04d88d87fmt: commit_nothing_staged test file
6af487afFix Smart-HTTP relative deepen and v0 shallow negotiation
6e7b8ebeAddress review: peel annotated tag shallow wants, split deepen paths
7257488aAddress review: error schema, submodule counts, message grammar
2172e245Fixes #1969: distinguish nothing staged from clean tree on commit
cd5e61e3Export rewrite apply recovery from history module
2807b097Fix crash-unsafe history rewrite apply with grit rewrite-apply journal
c45ea5afSpool checkout delete rollback to disk instead of RAM
3ef3efb7Roll back checkout deletions when a later unlink fails
f7db2a25Fix checkout deletes failing silently on permission errors
bafd590dFix merge driver review gaps: conflict %A, default union, add/add modes
958bdbf6Fix custom merge drivers ignored during tree merge
22498322Fixes #1967: follow subtree-merge renames in blame
a80d2fadAddress review: ServeOptions test init and object-info local lookup
727a8ee8Update public-api snapshot for ServeOptions server_options
fe62eb26Fixes #1946: advertise upload-pack v2 server-option, object-info, wait-for-done
ff2a9e14Fix push dropping remote sideband hook output
209d09dfFixes #1963: ISO-8859-1 working-tree-encoding clean/smudge
d2249a17Fix absolute fetch --depth and shallow clone by tag
7ea86decrefs: skip loose refs deleted during listing
20c85d59Fixes #1964: collapse expanded ident on clean (status/diff/commit)
daed43bewrite-tree: stop probing every cached subtree twice on each commit
4363c1ffodb: answer existence checks in batches through ObjectStore::contains_many