grit://cooking/changelog← what’s cooking
Wednesday, October 28, 2026 · release v0.5.24

108 changes landed on main

85 issue fixes · 23 other changes

v0.5.24 is mostly Git-compatibility and performance fixes, especially on Windows, plus new hook support, safer Ctrl-C/signal rollback, and rebase and history reliability.

  • ›New: merge directory-rename detection, blame of uncommitted edits (--contents), patch-id ranges and cherry-mark, reference-transaction hook
  • ›Hooks now run for local pushes, merge, commit, rebase and worktree add (post-checkout)
  • ›Ctrl-C, SIGTERM and SIGHUP roll back clone, switch, reset --hard and restore; rebase --exec recovers from interrupts
  • ›Windows speedups: status, add, clean, loose-object reads, long paths, auto-gc pipes (#2166, #2167, #2175)
  • ›Other speedups: reflog expiry (6 min to seconds on cargo), clone/fetch with 200k branches, bulk add memory, receive-pack memory
  • ›Fixes to rebase, history undo, patch-id vs git, ORIG_HEAD, gc, and credential handling
$ git log f7728c04..v0.5.24281 commits on GitHub ↗300+ files +12,430 −3,687
Highlightsmeasured by the reviewers, before → after
grit gc in a --shared clone (alternates) copies the whole alternate into a local pack: 11.9 s / 419 MB vs git 0.6 s
Resulting local pack size59427× better████████████████████████ 118,854 KiB █ 2 KiB
upload-pack include-tag walks the whole have history: every git fetch spends 0.58 s on git/git (git 15 ms)
git/git include-tag overhead over no-tag749× better████████████████████████ 524.2 ms █ 0.700 ms
grit diff/show/log -p of a modified 600 MB binary reads whole blobs: 1.2-1.9 GB RSS, 1 s (git: 5 MB, 0.00 s)
600 MiB tree-to-tree diff elapsed time499× better████████████████████████ 9.4 seconds (debug builds; system Git 0.0016 s) █ 0.019 seconds (debug builds; system Git 0.0016 s)
log -S / -G list pure renames as changing the string (git pairs renames first); log -S with a pathspec is 3-4x slower
git.git -n 25 -S the_repository -- builtin/ median167× better████████████████████████ 5,793 ms █ 34.7 ms
grit pull --ff-only exits 1 when history diverged; docs and merge --ff-only say 128 (refused_not_ff)
`pull --ff-only` refused JSON exit code128× better█ 1 exit code ████████████████████████ 128 exit code
issues fixed85
  • ›history undo after history fixup silently discards the staged edit; it survives only in an unreferenced-by-branch commit#216196719ff9

    History fixup and absorb now retain consumed index/worktree edits in an internal sidecar so undo restores the user's exact pre-command state and a second undo redoes the rewrite. The round-two fix extends that safety to staged submodule pointers while never reading their commits as blobs or touching the submodule checkout.

    grit history fixup median, exact branch base to branch: 8.2 ms → 7.6 ms (8% better)grit history undo after fixup median, exact branch base to branch: 6.2 ms → 6.4 ms (3% worse)
  • ›Reviewing a force-pushed branch: no range-diff or cherry equivalent, patch-id rejects ranges#2163176ebaaa

    Adds range expansion to patch-id and a cherry-marked symmetric revision walk that identifies patch-equivalent commits across rewritten branches, including one-to-one counterpart and message-change metadata. The round-two fix ensures `-n 0` cannot bypass the required `A...B` validation for cherry-mark or left/right modes.

    Cherry-mark median, system Git → branch grit (5k history / 400-commit one-line range): 8.0 ms → 29.4 ms (73% worse)
  • ›HTTPS to a non-GitHub host: never prompts for a password (even on a TTY) and grit auth <url> fails with HTTP 422#21598dbc58e7

    Adds a host-agnostic, non-interactive way to save HTTP(S) credentials with `grit auth --username --token`, distinguishes rejected credentials from missing credentials, and classifies non-grit hosts cleanly. The round-three fix prevents credential helpers or transports that close stdin early from killing remote-capable CLI commands with SIGPIPE.

  • ›grit restore --staged on a conflicted path silently resolves it; next grit commit records the conflict markers#21552e65faf2

    Index-only restore now refuses conflicted paths before dropping stages, and auto-staging refuses marker-bearing files whose stages were previously collapsed by another tool. Round 3 narrows the intentional-marker exemption so every complete worktree conflict block must exist byte-for-byte in HEAD with the same multiplicity; a committed example can no longer hide a new stash-pop block.

  • ›Windows: grit commit --staged of 10k-50k staged files (root commit) takes 46s/227s (git commit: 4s/18s)#216674d8bf80

    This change prevents detached Windows auto-gc from keeping the command caller's stdin/stdout/stderr pipes alive. It clears inheritance on grit's original three standard handles immediately before spawning the NUL-redirected background child, while leaving Linux/macOS and repository data behavior unchanged.

    10k Windows captured-output EOF (origin/main → branch): 3,397 ms → 385 ms (8.8× better)10k root commit median (system Git → reviewed grit, native Linux, gc.auto=0): 76.2 ms → 46.5 ms (64% better)
  • ›Windows v0.5.23: 50k-file tree, status/diff still ~4x slower than git (0.28 s vs 0.07 s), commit --allow-empty 4x#2167338da46c

    This change removes unnecessary case-insensitive filesystem path probes from the worktree scan used by status, diff, and commit. It resolves indexed paths lazily only after direct directory metadata cannot establish a match, while preserving case-folded fallback behavior and adding Git-compatibility regressions.

    50k clean status median (system git 40.145 ms): 54.3 ms → 47.6 ms (14% better)50k clean diff --name-status median (system git 19.061 ms): 32.4 ms → 25.6 ms (27% better)50k status with 100 edits median (system git 40.642 ms): 54.9 ms → 48.0 ms (14% better)50k diff --name-status with 100 edits median (system git 20.266 ms): 34.0 ms → 26.7 ms (27% better)
  • ›grit serve: full clone of a repo populated by two git pushes fails with "pack has 54 unresolved deltas"#21657f52cc6a

    This change prevents upload-pack from combining individually valid deltas from separate packs into an unresolvable cycle. It also refuses to stream a non-thin pack when a planned delta reaches the writer before its base, converting latent corruption into a typed error.

    Two-pack Cargo end-to-end clone time, Grit main vs fix median: 4.5 seconds → 3.9 seconds (17% better)Two-pack Cargo end-to-end clone median, system Git vs fixed Grit: 3.8 seconds → 3.9 seconds (1% worse)
  • ›switch_repository cancelled mid-checkout leaves a half-written worktree that looks like user edits; no rollback#21042074469c

    Windows console interrupts now reach the existing cancellation/rollback path used by switch and clone instead of immediately killing the process. The change adds platform handling, shared first/duplicate/later interrupt semantics, an end-to-end switch rollback regression, and concise command documentation.

  • ›grit add/commit panics (index out of bounds) when a tracked file becomes a directory; dir->file fails 'cannot inspect'#217048a95bd9

    Tracked paths that change between a file and directory now stage as a deletion of the old index path plus additions for the replacement, without panicking or surfacing ENOTDIR. Deferred removals are compacted in one pass, and CLI guidance only suggests closing programs when the OS actually reports an in-use file.

    Clean add median, current main to branch (interleaved 30 samples): 91.9 ms → 91.7 ms (0% better)500 modified files median, current main to branch: 100.5 ms → 101.2 ms (1% worse)5,000 deleted files median, current main to branch: 877.7 ms → 98.0 ms (9.0× better)
  • ›grit patch-id differs from git patch-id --stable when an inserted block can slide (30/150 cargo commits)#21720732913c

    Patch-ID computation now hashes the exact text hunks produced by Grit's Git-compatible default diff engine, including Git's placement of ambiguous insertions and deletions. The verbatim synthesis uses that same Myers/indent path, and whitespace hashing is batched by non-whitespace runs for speed.

    Cargo latest 150 non-merge patch-ID mismatches: 29 commits → 2 commits (15× better)Cargo HEAD~500..HEAD Grit median wall time: 541.7 ms → 341.5 ms (59% better)Grit/System-Git median wall-time ratio on Cargo range: 0.438 ratio → 0.297 ratio (47% better)
  • ›ignore: `!dir/` after `dir` does not re-include the directory; grit add/status skip 4 files git stages in the Linux tree#21687c3b6095

    Slash-free ignore rules now test only the queried path's basename instead of every path component. Parent-directory inheritance remains centralized in the existing ancestor walk, so a later `!dir/` genuinely re-includes that directory and its otherwise-unmatched children.

    Synthetic 12,007-file grit add median: 259.9 ms → 209.9 ms (24% better)Synthetic 12,007-file grit status median: 2.6 ms → 2.6 ms (2% better)Files staged by grit: 12,002 files → 12,007 files (0% better)
  • ›Concurrent local pushes creating the same new branch: second non-fast-forward push overwrites the first (lost update)#21743a9871f1

    Local-path pushes now create refs with a compare-and-swap that requires the ref to remain absent. If another pusher wins first, the loser exits nonzero with a typed already-exists result instead of overwriting a successful push.

    Local create push mean on current main plus/minus patch (25 pushes): 22.7 ms → 22.4 ms (1% better)Local create push median on current main plus/minus patch (25 pushes): 22.4 ms → 22.1 ms (1% better)
  • ›Ctrl-C during grit clone leaves a broken destination (.git without HEAD, or files without .git) that blocks retry#2162cbf627df

    Windows console interrupts now become cooperative cancellation, allowing clone and switch rollback to finish instead of letting the OS terminate midway. Failed-clone cleanup first drops pack/index mappings held by the calling thread, so Windows can remove the destination and an immediate retry can reuse the path.

    Dist local clone median, 4,000-file packed repo (9 runs): 131.7 ms → 134 ms (2% worse)Dist local clone ratio to system Git median (Git: 115.038 ms): 1.1 x git → 1.2 x git (2% worse)
  • ›diff/status report a file as modified (empty diff, exit 1) when only line endings differ and eol attr normalizes it away#21643cc25842

    Status and index-to-worktree diff now trust a worktree size mismatch only when the path's clean conversion is guaranteed to be byte-preserving. Conversion-sensitive paths are hashed, matching cleaned blobs refresh their index stat, and already-computed hashes are reused so real edits are not read twice.

    Identity-conversion status median, 10k files/1k edits: 32.0 ms → 32.6 ms (2% worse)EOL-only status median, 10k files/1k rewrites: 33.4 ms → 36.4 ms (8% worse)
  • ›grit rebase --exec 'git commit -a --amend' reports exec_dirty on a clean tree and --continue then fails 'HEAD changed'#2156540515d8

    Rebase exec now adopts commits created by amend/extra-commit commands and continues replaying later commits on the resulting HEAD. Recovery is resumable across dirty/conflict stops, preserves the original rewrite policy, executes pending commands after conflict skip, and rolls back cleanly if conflict-state persistence fails.

    40-commit/200-file rebase --exec true median wall time: 0.135 seconds → 0.134 seconds (1% better)40-commit/200-file rebase --exec amend median wall time: 0.243 seconds → 0.246 seconds (1% worse)40-commit/200-file rebase --exec true median peak RSS: 18,844 KiB → 18,612 KiB (1% better)40-commit/200-file rebase --exec amend median peak RSS: 18,756 KiB → 18,804 KiB (0% worse)
  • ›grit push to a local path / file:// remote skips pre-receive, update, post-receive hooks and rejects -o push options#21713b8f4052

    Local-path and file:// pushes now execute the destination repository's pre-receive, update, post-receive, and post-update scripts instead of bypassing server policy. New objects remain quarantined through pre-receive, hook refusals become structured rejected results with relayed output, and advertised push options reach the appropriate hooks.

    Incremental local push median, dist binary (30 runs): 4.4 ms → 4.8 ms (8% worse)No-op local push median, dist binary (50 runs): 2.1 ms → 2.3 ms (7% worse)
  • ›Windows: switch/reset --hard cannot delete a file with a >260-char path (os error 123)#2173554e74ca

    Checkout deletion rollback now stores each removed file under a short, process-and-sequence-based spool name instead of flattening the full repository-relative path into one filename component. The CLI also limits its close-the-file hint to actual sharing/lock failures, while regression coverage exercises switch and hard reset across a roughly 380-character tracked path.

    20k-file switch median (system git: 152.675 ms): 312.7 ms → 311.2 ms (0% better)
  • ›grit add / first commit of a large new tree peaks at ~4.6x git's RSS (300k files: 440 MB vs 96 MB)#21690024a125

    Bulk staging now hashes and stores untracked files in 4,096-path chunks, retaining only compact index entries until one final merge. A transient per-entry bit replaces the second path-keyed verification map for freshly hashed entries, and root commits derive their reported file list from the index instead of constructing a full empty-tree diff.

    60k-file dist add peak RSS: 100,080 KiB → 36,400 KiB (2.7× better)60k-file peak RSS relative to system Git: 5.2 x Git → 1.9 x Git (2.8× better)60k-file dist add elapsed time: 1.4 seconds → 1.2 seconds (18% better)Memory regression probe peak RSS: 100,400 KiB → 36,796 KiB (2.7× better)
  • ›Windows: first grit status after clone still 1.2-2.5 s on git/git (git 72 ms); #1914/#1304 regress again#217500764209

    This makes a clean first status after checkout stat-only on Windows by using NTFS nanosecond timestamps consistently for both stat matching and racy-entry detection. It also handles NTFS’s 100 ns tick and the last tick of a second safely, while preserving whole-second behavior under core.checkstat=minimal and when core.usenanosec=false.

    git/git first status mean: origin/main grit to review branch grit (core.usenanosec=true): 16.0 ms → 16.1 ms (1% worse)git/git first status mean: branch whole-second to nanosecond policy: 73.4 ms → 15.6 ms (4.7× better)git/git first status mean: system git to review branch grit: 162.1 ms → 16.1 ms (10× better)
  • ›Windows: grit add ./commit/lib stage() on a clean 3k-file repo take 7 s, 6k 27 s, 48k never (git: 40 ms)#210695781d56

    Adds a full-only `grit-lib` regression suite that prevents clean-tree staging under `core.ignorecase=true` from returning to quadratic behavior. It also checks no-op index compatibility with Git, one-file edit staging, repeated runs, and case-only spelling updates on case-folding filesystems; production code is unchanged.

    Clean stage time when fixture doubles from 2k to 4k files: 44.2 ms (2k → 4k) → 88.5 ms (2k → 4k) (50% worse)48k clean add: system git baseline → grit branch: 52.2 ms median → 64.0 ms median (18% worse)
  • ›revs --first-parent A..B keeps commits reachable from A via second parents; git excludes them#2177b6ffcf1f

    First-parent revision ranges now walk the commits to display through first parents only while hiding every commit reachable from excluded tips through any parent. The implementation computes a hidden frontier, stops the displayed walk there, and keeps the existing explicit first-parent-only exclusion mode unchanged.

    Correctness fixture listed commits (expected 2001): 4,001 commits → 2,001 commits (100% better)Correctness-shape median, commit-graph, debug build: 162.9 ms → 96.3 ms (69% better)Correctness-shape median, no commit-graph, debug build: 258.4 ms → 237.4 ms (9% better)Near-root exclusion median, commit-graph, debug build: 160.8 ms → 188.4 ms (15% worse)
  • ›grit rebase / history * move a `backup` branch sitting on the same tip, destroying the usual safety net#2182687ebc54

    History rewrites now leave any secondary local branch at the current HEAD commit pointing to the old history. The checked-out branch and true stacked branches on intermediate or descendant commits still move, including fast-forward rebases.

    Grit same-tip rebase median latency (debug build, 20 runs): 15.8 ms → 15.9 ms (1% worse)
  • ›grit diff/show/log pair renames at 40% similarity by default; docs say 50 (git: 50), so output differs from git#2183075d6bfd

    Changes the library’s default rename similarity threshold from 40% to 50%, making default diff/show/log behavior agree with the existing docs and system Git. Adds regression coverage for empty/true/copies config and a 44%-similar Git fixture, while preserving explicit lower thresholds.

    Issue fixture --stat median, equivalent 50% output: 2.3 ms over 100 warm runs → 2.3 ms over 100 warm runs (1% better)
  • ›history undo restores a staged CRLF file with a zero-stat index entry: git and grit then report MM#218869d42f41

    History undo now restores converted files with trustworthy index stat data instead of leaving a phantom unstaged modification. It preserves real unstaged edits by retaining an unset stat cache when clean-converted worktree content does not match the restored blob.

    history undo median, 2,000 files / 1,000 restored CRLF paths / 10 dist runs: 0.271 seconds → 0.283 seconds (4% worse)history undo peak RSS on the same fixture: 17,652 KiB → 17,716 KiB (0% worse)
  • ›blame ignores uncommitted edits: lines/content come from HEAD, no worktree or buffer-contents option in BlameOptions#2180890fd298

    Blame can now attribute a working-tree file or caller-supplied buffer without writing a synthetic object, preserving committed attribution while marking new lines with the null object id. The CLI defaults to working-tree blame unless a revision is explicit, adds --contents, and reports typed not-committed state in human and structured output. Round two additionally makes unfinished-merge overlays honor --first-parent.

    Clean-worktree blame median on grit-lib/src/blame.rs (15 runs): 101.8 ms → 105.3 ms (3% worse)
  • ›Custom ObjectStore has no commit-graph hook: walks of 120k commits 1.7-2.4x slower than the files backend#21849ee867d3

    Custom object stores can now build, persist, attach, and use verified commit-graphs without trusting the files backend's graph. History hot paths use backend-provided buffered reads, while malformed octopus graphs missing their EDGE chunk are rejected consistently with Git.

  • ›No ObjectStore view of a Repository's Odb: sources() is alternates only, primary() fails on custom stores#218703fab2b6

    Adds `Odb::read_store()` so embedders can expose everything an ODB can already read as one composable `Arc<dyn ObjectStore>`. It also makes Arc, Box, and references transparently forward read/write store traits, while retaining the narrower existing meanings of `sources()` and `primary()`.

    Packed ODB cat-file batch median (151,744 objects): 4,852 ms → 4,838 ms (0% better)
  • ›Remote::push ignores Cancel after start (file:// and smart HTTP); cargo push also 2x slower, 3.5x memory#217804ad33d4

    Push cancellation now reaches the expensive object walk, delta planning/writing, destination indexing, streamed smart-HTTP body, and smart-HTTP report wait. Local pushes stream directly into a quarantined temporary pack and avoid a redundant post-index pack verification, substantially reducing runtime and peak memory without changing pack or ref semantics.

    Cargo push median elapsed, main to branch: 7.8 seconds → 2.8 seconds (2.8× better)Cargo push median aggregate peak RSS, main to branch: 577,732 KiB → 257,612 KiB (2.2× better)Cargo push median elapsed, system git to branch: 3.9 seconds → 2.8 seconds (41% better)Cargo push median aggregate peak RSS, system git to branch: 195,376 KiB → 257,612 KiB (24% worse)
  • ›merge has no directory-rename detection; merge.directoryRenames ignored (git moves or flags the new file)#21792db83ea7

    Three-way merges now infer directory moves from file renames and correctly relocate files added by the opposite side. A typed policy supports off, apply, and conflict behavior; conflict metadata, index stages, worktree materialization, status output, and merge abort all preserve the resulting state safely.

    100-relocation merge dry-run median (optimized): 3.7 ms → 3.9 ms (7% worse)Plain merge dry-run median (optimized): 160.3 ms → 158.4 ms (1% better)100-relocation system Git vs reviewed grit median: 2.0 ms → 3.9 ms (49% worse)
  • ›Ref transactions and References::update accept refs to missing objects and branches to non-commits; git refuses#2176505df55c

    Ref updates issued through a Repository now reject missing object IDs, and branch/HEAD updates reject stored non-commit objects. Round 2 extends that policy to files-backend commit fast paths before any lock or reflog/ref write, including detached HEAD, replay, and the path-based branch writer.

    1,000 update_branch_for_commit_on_store operations, median of 3: 245.0 ms → 219.7 ms (12% better)
  • ›grit gc (and background gc --auto) deletes .git/refs after stash: git then says 'not a git repository'#2191fdb0e3d9

    This keeps the mandatory `.git/refs` directory when pack-refs removes the last loose references, including the common stash-triggered case. Empty directories below that root are still pruned, and the CLI remains a thin caller of the corrected library path.

  • ›grit add rewrites blobs that already exist in a pack as loose objects (300k duplicate loose objects, 1.2 GB)#219081a58c75

    Staging now detects when a worktree blob is already stored in a local pack, promisor pack, or alternate and freshens that copy instead of writing a duplicate loose object. Cruft-only objects and genuinely new content still become loose, so repository safety and partial-clone behavior are preserved.

    Packed re-add median, 20k files: 0.308 seconds → 0.143 seconds (2.2× better)Add 5k new files into packed repo median: 0.223 seconds → 0.228 seconds (2% worse)
  • ›grit gc / reflog expire on cargo takes 6 min as soon as a reflog entry is >30 days old (git: 0.9 s)#2192eb2680ab

    Ordinary reflog expiry now performs a commit-only, seen-set reachability walk instead of recursively validating every commit tree for every old entry. Full object completeness checking remains available for stale-fix, but is iterative and memoized across refs, eliminating repeated merge-diamond traversal and stack-depth risk.

    Reflog regression fixture (origin/main first case timeout vs branch all three cases): 60.2 seconds → 0.480 seconds (126× better)
  • ›clone/fetch of a repo with 200k branches is super-linear: 45 s vs git 1.6 s (clone), 45 s vs 11 s (fetch)#2189029fb74a

    This removes the quadratic same-batch ref-name check, makes initial clone refs a single packed-refs transaction, and eliminates duplicate ownership and filesystem work during ordinary fetch. The round-2 amendment preserves the public batch API's all-or-nothing behavior when a lock path is obstructed or otherwise errors.

    25k clone wall time, origin/main to branch: 2.7 s → 0.150 s (18× better)25k fetch wall time, origin/main to branch: 2.7 s → 1.4 s (94% better)200k clone wall time, origin/main to branch: 42.9 s → 1.4 s (31× better)200k fetch wall time, origin/main to branch: 41.8 s → 12.2 s (3.4× better)
  • ›RefStore::for_each_ref("") skips HEAD and other root refs on FilesRefStore but lists them on MemoryRefStore#2185620a96e1

    Empty-prefix ref enumeration now has one backend-independent meaning: enumerate only names under `refs/`, while non-empty prefixes can still select root refs such as `HEAD` and `ORIG_HEAD`. The change updates memory and reftable implementations, the public contract, custom-backend guidance, and shared conformance coverage.

  • ›grit diff (human and --markdown) prints a binary file as an empty section with no 'binary' note#2186e91d222f

    Worktree diffs now record a null worktree OID for actual content changes instead of reusing the index blob through a status-only shortcut. That lets existing human and Markdown renderers identify size-changing binary edits, while retaining the status fast path and keeping mode-only binary changes silent. Large index-to-worktree scans also use the existing parallel hashing path for every content-comparison caller.

    Dirty 50k-file diff, 1k size changes: 21.6 ms median (21 runs) → 22.9 ms median (21 runs) (6% worse)Dirty 50k-file diff --unstaged, 1k size changes: 21.9 ms median (21 runs) → 23.1 ms median (21 runs) (5% worse)Clean 50k-file diff: 16.8 ms median (21 runs) → 16.8 ms median (21 runs) (0% better)
  • ›grit rebase stopped by exec_dirty cannot be aborted, continued or skipped once the branch ref moves; repo stuck#2197c9b5a267

    A stopped `rebase --exec` now detects external ref movement before continue/skip can replay anything, and abort can safely restore a moved branch to its recorded pre-rebase tip. The CLI reports the overwritten tip in human, Markdown, and JSON output while preserving the previous output for ordinary aborts.

  • ›includeIf hasconfig:remote.*.url: never matches for repo-local remotes: grit commits with the wrong identity#2199a4760350

    This makes `includeIf "hasconfig:remote.*.url:…"` evaluate against every remote URL in the current config load, so a global identity include can see remotes defined later in `.git/config`. Matching includes still merge at their original position, preserving later local overrides.

    grit config user.email median, 1,000 release runs: 4.3 ms → 4.4 ms (2% worse)
  • ›grit gc / reflog expire on cargo takes 6 min as soon as a reflog entry is >30 days old (git: 0.9 s)#21922a2c86d7

    Reflog expiry now interns every commit read during one expiry pass into a dense index and reuses its parent linkage across all refs. Each ref still has its own reachability bitmap, so the speedup does not leak reachability between branches.

    Cargo 23,588-commit, 300-old-ref dry-run median wall time (origin/main to branch, 5 runs): 0.998 seconds → 0.234 seconds (4.3× better)Same Cargo benchmark median wall time (system Git to branch, 5 runs): 0.734 seconds → 0.234 seconds (3.1× better)
  • ›Concurrent commits to different branches fail ~55% with LockHeld on an injected MemoryRefStore (global lock, no retry)#2181b334ea20

    Adds one bounded retry boundary around ref-store prepare operations so pluggable stores with a single writer slot serialize concurrent writes instead of failing most commits. It also shortens the shared-lock critical section for linked worktrees and makes LockHeld consistently identify the caller’s ref.

    Successful commits in 8×100 linked-worktree reproduction: 455 commits → 800 commits (76% better)10k files-ref batch median: 833 ms → 843 ms (1% worse)
  • ›Ref dir/file conflicts give raw 'I/O error: Access is denied' / 'corrupt ref store'; fetch aborts without hint#219814e6e269

    Ref directory/file collisions now surface as structured name conflicts that identify both the requested ref and the blocking descendant. Reflog-path collisions are detected during transaction preparation, so branch/switch errors are useful and fetch can continue applying unrelated updates while rejecting the conflicting tracking ref with prune guidance.

    10k reflogged creates transaction median: 702.1 ms (7 runs) → 711.6 ms (7 runs) (1% worse)10k reflogged creates relative to system Git: 1.5 x system Git median → 1.5 x system Git median (1% worse)
  • ›grit patch-id differs from git patch-id --stable when an inserted block can slide (30/150 cargo commits)#2172b63df8b1

    This makes diff compaction distinguish a line ending in LF from an otherwise identical final line without LF, preventing ambiguous change groups from sliding across EOF. It also narrows patch-ID whitespace stripping to Git’s actual four-byte set, restoring compatible IDs for control-character changes.

    Cargo patch-id median, 170 commits (current main → branch): 34.1 ms → 32.5 ms (5% better)
  • ›grit pull --ff-only exits 1 when history diverged; docs and merge --ff-only say 128 (refused_not_ff)#220876ce3c28

    Maps a refused fast-forward pull directly to the typed `RefusedNotFf` status, producing exit 128 in human, JSON, and markdown modes. Conflict and dry-run/check behavior remains at exit 1, and generated skill/docs now state the same contract.

    `pull --ff-only` refused JSON exit code: 1 exit code → 128 exit code (128× better)
  • ›grit patch-id differs from git patch-id --stable for every commit that adds or deletes an empty file#22065931549d

    This change makes ODB-computed patch IDs omit invented unified-diff body lines for empty-file additions/deletions and reproduces Git's stable digest boundaries for header-only entries. It adds a system-Git regression covering nine empty, mode-only, rename, ordering, and truncation shapes across stable, unstable, and verbatim modes.

    Cargo 2,200-revision-range patch-id mismatches: 61 commits → 7 commits (8.7× better)Cargo range median wall time (5 warm runs): 0.940 seconds → 0.930 seconds (1% better)
  • ›switch / merge --ff keep the old gitlink in the index when the submodule is ahead: staged revert of the bump#22007cc16948

    This corrects index handling when switch or fast-forward merge moves a submodule gitlink while the submodule checkout has drifted. The index now records the target gitlink without moving or deleting an existing submodule directory, including an uninitialized directory containing local files.

  • ›SIGTERM/SIGHUP during grit clone leaves a partial .git that blocks retry; only SIGINT is handled#22094f53a9a1

    Unix grit commands now treat SIGTERM and SIGHUP like Ctrl-C while a cancellable operation is active: the first signal requests cancellation so clone or switch can roll back, while a deliberate later signal restores default termination. Clone retry behavior and switch atomicity are covered end-to-end, and user docs now explain the cleanup guarantees.

    Unix cancellation signals handled: 1 signals → 3 signals (3.0× better)
  • ›history drop/reword fail on conflicts from an unrelated sibling branch; error names no branch or commit#2204e8e3d00a

    History rewrite failures now identify the exact replay commit and any sibling branch that made planning fail. Users get a direct `--update-refs head` escape hatch in human and structured output, while successful planning and the default branch-moving behavior are unchanged.

  • ›grit patch-id fails 'revision <oid> not found' on any commit that changes a submodule gitlink#2205b89b9f54

    Gitlink entries now contribute `Subproject commit <oid>\n` to patch IDs instead of being looked up as objects in the parent repository. The same side loader is used by stable, unstable, rename, binary-probe, and verbatim paths; type changes are represented as deletion plus addition to match Git's emitted patch.

    Cargo HEAD~2000..HEAD paired median: 0.997 seconds → 0.972 seconds (3% better)
  • ›grit rebase of 1 commit onto a main 600 commits ahead takes 6.5 s on git/git (git: 1.1 s); apply phase is the cost#2211b4b486c5

    Clean worktree paths involved in rewrite checkout are now represented by path and kind in one journal manifest instead of one full-byte file per changed path. If apply is interrupted, recovery rematerializes those paths from the pre-apply tree; paths carrying preserved local edits still retain exact byte snapshots.

    1,500-file one-commit rebase median (Linux debug, 6 runs): 251.0 ms → 229.0 ms (10% better)
  • ›upload-pack sparse:oid filter omits blobs git sends (nested dirs under a matched dir); 64 s on cargo vs git 13 s#2212856bfc60

    Corrects non-cone sparse filtering so nested blobs under an included directory are no longer omitted when `!/*/` is present, and unanchored patterns follow Git basename matching. It also avoids reparsing sparse patterns for every blob and adds cancellation polling to filtered object enumeration when callers provide a signal.

  • ›grit gc on a server repo fails 'corrupt object: pack is not closed under reachability' whenever pushes land concurrently#22104de05aea
  • ›grit merge skips pre-merge-commit, prepare-commit-msg and commit-msg hooks; commit skips prepare-commit-msg#22013b583a06

    This change completes commit, merge, pull, and rebase hook handling in grit-lib and exposes only the required --no-verify controls in the CLI. It also preserves merge state and autostashed edits when a merge-commit hook refuses, allowing a safe continue or abort.

    Clean merge median (debug build, 30 runs): 8.8 ms → 8.9 ms (2% worse)Clean merge ratio vs system Git (4.143 ms median): 2.1 x → 2.2 x (2% worse)
  • ›StatusModel.unstaged entries carry new_oid == old_oid; DiffEntry docs say the worktree side is the null OID#22153b583a06

    Status callers can now distinguish an un-hashed modified worktree file from the staged blob: the worktree side is represented by the null OID instead of incorrectly repeating the index OID. The optimization remains intact and still avoids reading file content when an identity-conversion size mismatch proves modification.

  • ›kill -9 during rebase apply leaves a stale index.lock that blocks crash recovery and --abort (core.lockfilePid off)#22146a3f33f2

    Grit now records the process ID beside every grit-owned index lock unless explicitly disabled. After a process is killed during rewrite checkout, status or abort can identify that dead holder, safely remove the stale lock pair, reacquire the index lock, and finish or roll back recovery without manual file deletion.

  • ›log -S / -G list pure renames as changing the string (git pairs renames first); log -S with a pathspec is 3-4x slower#2203ed2632f4

    Pickaxe history search now limits the tree diff to the requested paths, pairs adds/deletes as renames, and only then applies -S/-G. The same change reuses commit/blob data and enables bounded path walks to stop after enough matches, removing the reported false positives and most of the 3–4x pathspec gap.

    git.git -S the_repository -- builtin/ median: 5,729 ms → 2,116 ms (2.7× better)git.git -n 25 -S the_repository -- builtin/ median: 5,793 ms → 34.7 ms (167× better)git.git -S sha1_file -- cache.h median: 723.5 ms → 315.6 ms (2.3× better)git.git -n 30 -G cmd_commit -- builtin/commit.c median: 453.4 ms → 399.7 ms (13% better)
  • ›receive-pack unpack path (receive.unpackLimit) holds ~14 KB per object: 190k-object push peaks at 3.0 GB (git: 20 MB)#2216d9d6a598

    Loose receive-pack ingestion now stores only compact object metadata for the life of the pack, resolves available deltas during the forward scan, and retains base bytes in a 96 MiB LRU. Strict graph and dot-special validation re-read stored objects as needed, preserving compatibility while removing payload retention proportional to object count.

    190,795-object cargo push peak server RSS: 3,027,088 KiB → 188,036 KiB (16× better)190,795-object cargo push server elapsed time: 25.0 seconds → 27.4 seconds (9% worse)Fixed grit versus system Git elapsed time (Git before, grit after): 42.6 seconds → 27.4 seconds (55% better)
  • ›reset, merge and rebase never write ORIG_HEAD: `grit reset --hard ORIG_HEAD` recovery is impossible#2202f6018de1

    This change makes ORIG_HEAD a repository-ref-store-backed recovery point across reset, merge, pull, and rebase. Round two closes the remaining documented rebase gaps by moving `--exec` orchestration into grit-lib and routing `-X ours|theirs` through the resumable rebase session, including continuation with the saved merge favor.

  • ›rebase --update-refs branches leaves stacked branches above HEAD behind unless each is exactly one commit on its parent#221364894066

    Rebase planning now extends the replay order with all commits on scoped branch tips that descend from the rewritten range, not just a single eligible tip or a first-parent chain. Multi-commit stacked branches and stacked merges are replayed after the checked-out range, so every affected local branch moves together.

    stacked refs reported by issue reproduction: 1 refs → 3 refs (3.0× better)valid adjacent ancestry links in main→a→b→c reproduction: 2 of 3 links → 3 of 3 links (50% better)
  • ›Follow-up: Fix #2210: grit gc on a server repo fails 'corrupt object: pack is not closed under reachability' whenever pushes land concurrently#2210e4f5b538
  • ›upload-pack include-tag walks the whole have history: every git fetch spends 0.58 s on git/git (git 15 ms)#2196e71150ea

    Include-tag decisions now happen after pack enumeration by probing the objects actually selected for transmission. Annotated tag chains are appended only when their final commit, tree, or blob is in that set, on both normal walk and bitmap-reuse paths, without walking client history.

    git/git include-tag median (current main to candidate): 652.1 ms → 131.5 ms (5.0× better)git/git include-tag overhead over no-tag: 524.2 ms → 0.700 ms (749× better)git/git no-tag median: 127.9 ms → 130.8 ms (2% worse)
  • ›rebase conflict markers label the replayed side with the file path (>>>>>>> f.txt) instead of the commit#220798ca92aa

    Rebase conflicts now identify the replayed commit in incoming markers instead of repeating the file path. A shared internal label helper keeps pick and history replay aligned, including linear replay, merge replay, and the post-`--exec` re-merge path.

    500-commit optimized grit rebase wall time: 100.4 ms median of 7 → 100.9 ms median of 7 (1% worse)500-commit system Git vs review-branch grit wall time: 359.2 ms median of 7 → 100.9 ms median of 7 (3.6× better)
  • ›Any HTTP 4xx/5xx (typo'd repo URL: 404) after authenticating erases the valid stored credential for the host#222168ebabf3

    Authenticated HTTP retries now erase a credential only when the server answers 401 again. Repository-not-found, permission, rate-limit, and server errors remain typed HTTP failures while leaving both the provider entry and in-process authorization state intact.

  • ›grit worktree add never runs the post-checkout hook (git does, with a null old oid); LFS/hook setups skip it#2232b4bb1cf1

    Linked worktree creation now runs `post-checkout` after checkout and after rollback guards are committed. The hook executes from the new worktree with `<null oid> <new HEAD> 1`; no-checkout and orphan creation continue to skip it, and hook failures remain advisory.

  • ›grit-lib: merge::integrate_branch returns Error::Message prose for dirty-tree overwrite instead of typed WouldOverwrite#22317ad686f5

    Merge checkout refusals now expose a structured reason and every affected path instead of forcing library callers to parse English. The same typed error is used for untracked checkout collisions, and history replay consumes it directly.

  • ›commit -- <path> on a case-colliding clone (macOS) records a deletion of the tracked file; commit vs status disagree#2226162c9baa

    Case-colliding tracked paths now retain their original index entries when only the shared case-folded worktree file exists. Path-scoped commit, auto-stage commit, and status all consistently treat the collision loser as a checkout artifact rather than a deletion or modification.

    Case-colliding tracked paths retained after attempt: 1 paths → 2 paths (2.0× better)20k-file core.ignorecase clean status median (system Git reference 13.787 ms): 44.7 ms → 44.4 ms (1% better)
  • ›Cloning a repo with submodules gives no hint and no submodule command; status says modified where commit says clean#222907f5bd68

    Uninitialized submodule directories now remain clean even when stray files are placed inside them, matching system Git and eliminating the status/commit disagreement. Clone results also report gitlink paths, while human and Markdown modes explain that grit does not fetch submodules and JSON exposes a stable `submodule_paths` field.

  • ›reference-transaction hook never runs: no grit command fires it, so a denying hook cannot block ref updates#222031069050

    This change makes reference updates invoke the repository's reference-transaction hook once per transaction, with prepared followed by committed or aborted and Git-compatible old/new/ref input. A denying prepared hook now prevents writes, including central ref-store updates and files-backend commit/delete bypass paths; round three restores a missing test import and repairs the nested-lock test's stdin handling.

  • ›grit clone --blobs none against a server that ignores filters downloads everything but reports a blobless partial clone#22338533a4ba

    Clone and fetch outcomes now expose whether a requested object filter was sent after capability negotiation. `grit clone --blobs` warns and suppresses partial-clone claims when the server lacks filter support, while retaining the existing Git-compatible promisor configuration.

  • ›grit-lib sets no rust-version: on stable < 1.95 the build fails with 'if let guards are experimental'; MSRV undocumented#2227e565cd5d

    Declares Rust 1.95 as the minimum consumer toolchain for all four publishable Grit crates, so older Cargo versions fail immediately with a useful requirement instead of reaching an experimental-language-feature compiler error. It separately documents that contributors use the repository's Rust 1.99 pin and adds a manifest regression test.

  • ›Ctrl-C during grit reset --hard / restore --source leaves stale index.lock and a half-reset worktree#22346f396445

    Reset and path restore now honor Ctrl-C through the library cancellation path, release index locks, and roll back worktree changes instead of leaving an index/HEAD/worktree split. The round-2 fix extends the checkout journal to preserve a whole existing directory—including untracked files—when a restore temporarily replaces that directory with a tracked file.

  • ›clone via grit serve bundle: git/git 33 s/946 MB vs 9.9 s/443 MB without bundle (serial pack verify)#222885502901

    Bundle ingestion now trusts the validation already completed by the index-building pass instead of serially inflating every object a second time. It preserves all installed pack/index formats and leaves explicit verify-pack/fsck behavior unchanged.

    git/git 310 MiB bundle unbundle median wall time: 43.3 seconds → 5.5 seconds (7.9× better)git/git 310 MiB bundle unbundle median peak RSS: 587,488 KiB → 73,884 KiB (8.0× better)
  • ›ref@{yesterday}, @{last week}, @{noon yesterday} are 'unknown revision'; only N.units.ago and ISO dates work#22241daa56ce

    Reflog date selectors now use the same relaxed date grammar as other date-aware library operations, so forms such as `yesterday`, `last week`, and `noon yesterday` resolve correctly. Invalid date text survives the object-inspection API as a typed error, and dates older than the reflog resolve to the oldest state while emitting a structured warning.

  • ›grit log --since combined with --author/--grep/pathspec walks all history: 1.3 s vs git 80-96 ms on git/git#22305cabe3d7

    Filtered and path-limited default-order revision walks now stop at the `--since` date frontier instead of traversing all reachable history, including filtered count mode. Round 3 adds an immediate zero result for `--count -n 0`, preventing the bounded filter walk from counting its first match.

    git/git debug median: since + author (origin/main → branch): 8,556 ms → 502.1 ms (17× better)git/git debug median: since + grep (origin/main → branch): 8,380 ms → 503.0 ms (17× better)
  • ›Ctrl-C (or kill) during grit rebase --exec leaves a detached HEAD with no rebase state; --abort says none in progress#2235f1ef4bdb

    This makes `rebase --exec` write durable recovery state before detaching HEAD and keep its command cursor current while subprocesses run. An interrupted process is now visible in `grit status`, can be aborted back to the original branch/index/worktree, or continued from the recorded replay tip.

    15-commit debug rebase --exec true median: 108.2 ms → 135.5 ms (20% worse)
  • ›Common git spellings (status -s, log -1, tag -a, commit -m x -m y, cherry-pick) fail with misleading or no hint#2238298ff233

    This change recognizes a small set of common Git spellings that map cleanly to existing Grit behavior and replaces misleading clap suggestions with Grit-specific migration hints. The round-two fixes preserve reset hint formatting and make message-less annotated-tag requests fail consistently as structured usage errors.

    Round-two expected CLI outcomes reproduced: 1 of 3 cases → 3 of 3 cases (3.0× better)
  • ›grit add (human and --json) reports mtime-only changes as Staged/modified; git add stages nothing#224172361d45

    Mtime/ctime-only changes now refresh tracked index stat metadata without being reported as staged content. The index refresh is persisted for both `grit add` and auto-staging commit flows, while actual content and mode changes retain their existing staged behavior.

  • ›grit writes UNTR index extension that git rejects: every `git status` warns 'untracked cache is disabled' (>=5k files)#2240afe9a0b0

    Normalizes the worktree component of grit’s UNTR cache identity and emits Git’s `Windows` system name on Windows. This lets system Git accept grit-written untracked-cache extensions instead of warning and discarding them on every status.

  • ›grit blame -C / -C -C / -C -C -C has no effect: copied and moved lines are never traced to their source file#22394274a244

    This change makes repeated `grit blame -C` levels trace moved and copied chunks back to their source files, including groups of individually short lines. The round-2 follow-up restores performance by comparing candidate blob content first and recursively blaming only the selected source.

    12-commit, 101-candidate `blame -CCC` median: 11.4 ms (origin/main vs reviewed branch, 15 warmed debug runs) → 10.6 ms (origin/main vs reviewed branch, 15 warmed debug runs) (7% better)
  • ›grit gc in a --shared clone (alternates) copies the whole alternate into a local pack: 11.9 s / 419 MB vs git 0.6 s#22231e2a253e

    GC and full repack now treat alternate object stores as reachability boundaries, so shared clones pack only objects physically owned by the clone. The round-2 correction makes prune-packed distinguish alternate pack membership from alternate loose membership, preserving local loose duplicates in the latter case to match Git.

    Shared-clone gc elapsed time: 25.7 seconds → 0.438 seconds (59× better)Shared-clone gc peak RSS: 987,852 KB → 42,260 KB (23× better)Objects copied into local pack: 132,045 objects → 3 objects (44015× better)Resulting local pack size: 118,854 KiB → 2 KiB (59427× better)
  • ›Windows: cold read of fresh loose objects is ~25x slower than git (gc 143 s vs 10.6 s, fsck 39 s vs 1.4 s, 31k objects)#2243d76aad42

    This change sizes Windows loose-object reads to the compressed file bytes remaining, avoiding oversized 64 KiB reads that trigger severe cold-file overhead. It also makes fsck validate and retain each loose object only until its ordered result is consumed, eliminating both the duplicate read and the prior unbounded inflated-payload retention.

    64×2 MiB loose-object fsck wall time (origin/main → branch, debug build): 1.4 seconds → 0.307 seconds (4.6× better)64×2 MiB loose-object fsck peak RSS (origin/main → branch, debug build): 55,236 KiB → 61,200 KiB (10% worse)64×2 MiB loose-object fsck wall time (system git → branch): 0.327 seconds → 0.307 seconds (7% better)64×2 MiB loose-object fsck peak RSS (system git → branch): 8,676 KiB → 61,200 KiB (86% worse)
  • ›grit add <path> on a conflicted submodule stages an empty 100755 blob; rebase --continue commits it over the gitlink#222227c182d2

    Conflicted submodule paths now stage the checked-out submodule commit as a mode-160000 gitlink instead of an empty executable blob. Status no longer mislabels conflicted submodules as untracked, restore can check out either conflict side, and round 3 keeps ordinary `grit add .` on its original fast matcher path.

    50k-file clean `grit add .` median: 201.8 ms → 202.0 ms (0% worse)
  • ›Windows: grit clean --dry-run is 10x and status --ignored 6x slower than git on 60k untracked + 60k ignored files#224231e3eeea

    This change makes ignored directory excludes opaque to the untracked walker when Git would report the directory as one unit, then carries that proof into clean so it can avoid redundant preservation walks. It also uses directory-entry type data during required rescans and avoids unnecessary ignore-file metadata probes while preserving symlink handling.

    clean --dry-run median, 120k files: 353.3 ms → 215.9 ms (64% better)status --ignored median, 120k files: 376.0 ms → 163.8 ms (2.3× better)clean --dry-run --only-ignored median, 120k files: 384.5 ms → 161.2 ms (2.4× better)clean --dry-run / system Git ratio: 2.9 x → 1.8 x (63% better)
  • ›grit diff/show/log -p of a modified 600 MB binary reads whole blobs: 1.2-1.9 GB RSS, 1 s (git: 5 MB, 0.00 s)#223731e3eeea

    Large diff sides now use object headers or worktree metadata to classify files over `core.bigFileThreshold` without materializing their contents. The follow-up preserves Git-compatible forced-text behavior when a path selects a diff driver configured with `binary=false`.

    600 MiB tree-to-tree diff elapsed time: 9.4 seconds (debug builds; system Git 0.0016 s) → 0.019 seconds (debug builds; system Git 0.0016 s) (499× better)600 MiB tree-to-tree diff peak RSS: 1,885,620 KiB (system Git 11740 KiB) → 42,100 KiB (system Git 11740 KiB) (45× better)
  • ›grit --stat/--numstat and log -G align lines differently from grit's own patch (stat says 32 deletions, patch 1)#2236dae3135f

    This change routes stat/numstat and `log -G` through the same xdiff-derived line alignment used by patches, with regression coverage against system Git. Round 3 replaces whole-file changed-mask expansion with direct middle-mask counting and special-cases uniform unchanged affixes, improving the previous revision but leaving a measured 30.7% hot-path regression versus origin/main.

    Criterion log_stat/stat/100k_lines_small_edit median-of-medians: 2.3 ms → 3.0 ms (23% worse)
  • ›Follow-up: Fix #2236: grit --stat/--numstat and log -G align lines differently from grit's own patch (stat says 32 deletions, patch 1)#22367071b202

    This aligns stat/numstat and `log -G` line selection with Grit’s own compacted patch alignment, while retaining a specialized LF-only count path for large files with small edits. The round-2 follow-up is mechanical: it removes one unnecessary mutable closure binding and replaces four manual membership scans with slice `contains`, allowing the enforced lint gate to pass.

other changes23
  • ›Quality #49: Move logic into grit-lib30718697

    Partial stash pushes now resolve hunk or line selections once inside grit-lib and return the resolved hunk IDs/line labels in both preview and push outcomes. The CLI no longer performs a duplicate unstaged diff, and the include-untracked conflict is enforced consistently for library callers.

    partial stash dry-run median: 47.6 ms → 26.2 ms (82% better)partial stash real push median: 44.3 ms → 25.3 ms (75% better)daily stash push-pop median: 125.0 ms → 123.1 ms (2% better)
  • ›Quality #50: Deduplicate536b8dfa

    Centralizes integration-test system-git execution, revision parsing, and localhost port selection in grit-test-support. Forty-nine test modules now import shared fixtures instead of maintaining local copies, reducing the change set by 1,023 net lines while preserving every test declaration.

    Named git/rev_parse/free_port implementations in changed Rust files: 104 implementations → 18 implementations (5.8× better)
  • ›Speed: log/json-100@git-git4ad55670

    Commit walks now inflate fallback commit objects into one reusable buffer instead of allocating and retaining session-cache copies per commit. JSON log output separately avoids constructing human-only summary rows and defers diff config loading until a diff format actually needs it.

    log/json-100 same-run median: 4.5 ms → 4.1 ms (8% better)log/json-100 same-run peak RSS: 21,200 KiB → 21,404 KiB (1% worse)log/json-100 system-git vs branch median: 3.0 ms (git then grit branch) → 4.1 ms (git then grit branch) (28% worse)
  • ›Quality #51: Simplifyb69fb834

    Deletes the unused Git-style diffstat layout engine and its two public wrapper functions from grit-lib. It also removes the now-unneeded unicode-width workspace dependency and cleans up tests, coverage metadata, and the public API snapshot; grit-cli output is unchanged because it uses its own renderer.

  • ›Speed: show/head@git-gitbd7cc293

    Tree-to-tree diffs now merge-walk borrowed entries directly from raw tree payloads, avoiding per-entry name allocations while retaining parsed trees for callers that need owned entries. The same change makes the ODB session tree cache hold either raw or parsed trees under one byte budget, fixes custom-target `dist` profile reporting, and documents the daily benchmark profile and startup-cost conclusion.

    release current origin/main→branch show/head@git-git median: 2.9 ms, 400 interleaved direct spawns → 2.9 ms, 400 interleaved direct spawns (1% better)release current origin/main→branch show/head@git-git peak RSS: 17,424 KiB, max of 400 runs → 17,224 KiB, max of 400 runs (1% better)release current origin/main→branch show/head@big-blobs median: 2.6 ms, 400 interleaved direct spawns → 2.6 ms, 400 interleaved direct spawns (1% worse)release current origin/main→branch show/head@big-blobs peak RSS: 16,124 KiB, max of 400 runs → 15,880 KiB, max of 400 runs (2% better)
  • ›Quality #52: Library API qualitydc920890

    Hunk selection now retains the underlying `grit_lib::Error` instead of flattening it to text, and exposes typed side, gitlink, path, and mode failures. The CLI only extends its exhaustive exit-code mapping, while new library tests prove callers can branch on the typed failures.

    add/hunks@large-file median wall: 24.0 ms → 23.4 ms (3% better)add/lines@large-file median wall: 37.6 ms → 37.0 ms (2% better)restore/hunks@large-file median wall: 23.0 ms → 22.1 ms (4% better)add/hunks@large-file peak RSS: 29,995,008 bytes → 29,884,416 bytes (0% better)
  • ›Perf regression 2026-10-27: commit/staged@synth-100k, diff/staged@deep-tree, diff/worktree@deep-tree +41c526550

    Replaces one shared mutex/hash-map ancestor-symlink cache with per-scan-worker prefix caches and, on 64-bit Linux, confirms clean deep directory chains with one openat2 walk. It retains component-by-component fallback for symlinks, missing paths, unsupported kernels, case folding, and Unicode resolution, and adds correctness plus probe-count guards.

    diff/worktree@deep-tree wall (git control 102.072 ms): 217.8 ms → 72.5 ms (3.0× better)diff/worktree@deep-tree peak RSS: 69 MiB → 42.7 MiB (62% better)status/clean@deep-tree wall (git control 1114.851 ms): 662.1 ms → 522.3 ms (27% better)status/dirty@deep-tree wall (git control 1194.255 ms): 685.5 ms → 564.8 ms (21% better)
  • ›Speed: revs/author-n50@deep-historybb6f58cf

    The change avoids constructing full commit JSON and ref decorations when `grit revs` is rendering human or Markdown output, defers filter subprocess environment construction until a filter actually runs, and removes allocation from ObjectId display. It also records the benchmark evidence in the documentation and baseline bundle.

    revs/author-n50 no graph wall: 3.3 ms median → 3.1 ms median (8% better)revs/author-n50 no graph peak RSS: 20,708 KiB → 19,092 KiB (8% better)revs/author-n50 commit-graph wall: 3.4 ms median → 3.1 ms median (7% better)revs/author-n50 commit-graph peak RSS: 23,508 KiB → 21,316 KiB (10% better)
  • ›Quality #53: Move logic into grit-libf46206c8

    Adds a reusable grit-lib diffstat model containing per-file metadata, line counts, aggregate totals, maximum-change scaling, and typed bar widths. Diff, show, log/shortlog, and stash-show CLI paths retain their output contracts but delegate counting and scaling to the library.

    diff/stat@synth-100k median (system git 112.04 ms): 72.8 ms → 73.5 ms (1% worse)diff/worktree@synth-10k median (system git 7.71 ms): 7.8 ms → 8.1 ms (3% worse)log/stat-n50@git-git median (system git 100.15 ms): 168.3 ms → 167.7 ms (0% better)show/head@git-git median (system git 2.07 ms): 2.8 ms → 2.6 ms (8% better)
  • ›Speed: reset/sparse@sparse-unique-120k6f42b82e

    Hard reset now updates an absent out-of-cone sparse-directory index entry directly to the destination tree instead of materializing, diffing, probing, and collapsing all descendants. A crate-private diff mode avoids reading either excluded subtree, while switch retains its stricter expansion behavior.

    reset/sparse@sparse-unique-120k wall time: 905.0 ms → 24.8 ms (36× better)reset/sparse@sparse-unique-120k peak RSS: 95.8 MB → 22.4 MB (4.3× better)reset/sparse@sparse-120k wall time: 24.6 ms → 24.3 ms (1% better)reset/sparse@sparse-240k wall time: 24.7 ms → 24.6 ms (0% better)
  • ›Speed: switch/sparse@sparse-unique-120k7af96b50

    This replaces per-directory whole-index deletion and sorted insertion during sparse-index collapse with a deepest-first decision pass over a virtual collapsed view, followed by one rebuild of the entry vector. It also caches prefix-tree resolution, uses the ODB tree-walk cache, and adds unit plus system-Git round-trip coverage and benchmark documentation.

    switch/sparse@sparse-unique-120k wall (system Git controls 81.8→82.2 ms): 786.9 ms → 312.6 ms (2.5× better)switch/sparse@sparse-unique-120k peak RSS: 59.0 MB → 59.1 MB (0% worse)reset/sparse@sparse-120k wall: 26.4 ms → 26.6 ms (1% worse)reset/sparse@sparse-240k wall: 29.9 ms → 29.2 ms (2% better)
  • ›Quality #54: Dead code and dependenciesa90c1215

    Removes seven unreferenced grit-lib functions and narrows one internal pack-index writer, reducing the library by 124 net lines. It also instruments actual MIDX metadata probes in test-only counters and reuses the shared HTTP media-type parser for non-Git-server detection.

    clone-git-http-prod grit median: 398.2 ms → 392.4 ms (1% better)clone-git-http-prod system git control: 544.7 ms → 535.0 ms (2% better)clone-grit-http-prod grit median: 568.5 ms → 589.0 ms (3% worse)
  • ›Speed: reset/undo-commit@git-git8cbefdd2

    This change accelerates reachability paints seeded by hundreds of refs by reading only packed commit header blocks in parallel and caching their parent/date linkage. It also short-circuits the common reset-backward ancestry check along the first-parent chain, while retaining full-read and full-walk fallbacks.

    reset/undo-commit wall time: 13.3 ms median (20 runs) → 7.3 ms median (20 runs) (82% better)reset/undo-commit peak RSS: 46.4 MiB → 29.3 MiB (58% better)reset/undo-commit system Git control: 2.1 ms median (20 runs) → 2.1 ms median (20 runs) (1% better)reset/hard@synth-10k wall time: 26.5 ms median (20 runs) → 26.7 ms median (20 runs) (1% worse)
  • ›Main keeper: make CI on main green (public-api)78cd7ed8

    Reorders two adjacent PackBitmapWriteOptions field entries in the committed grit-lib API snapshot so it matches deterministic cargo-public-api output. No Rust source, behavior, public API shape, on-disk format, wire protocol, test, or documentation content changes.

  • ›Speed: restore/hunks@large-file144da670

    This change makes worktree hunk restoration hold and load one index snapshot, then avoids rewriting `.git/index` when the restored file still differs from its index entry. It also removes redundant large-file diff copies/scans and shortens xdiff temporary lifetimes, reducing the target runtime substantially without changing public API or on-disk formats.

    restore/hunks@large-file median wall: 25.0 ms → 13.8 ms (81% better)restore/hunks@large-file grit/Git ratio: 3.2 x → 2.0 x (61% better)restore/hunks@large-file peak RSS: 24.5 MiB → 24.6 MiB (0% worse)restore/paths@synth-10k median wall: 12.9 ms → 11.3 ms (14% better)
  • ›Main keeper: make CI on main green (rustdoc, check-windows-msvc, clippy, test)22ec3d07

    This removes stale pieces left behind when receive-pack migration moved to guard objects and repack concurrency moved to an inventory snapshot plus `gc_test_hooks`. It also corrects three private helper return paths to match their existing `Result<()>` signatures, restoring compilation without changing successful runtime behavior.

  • ›Main keeper: make CI on main green (check-windows-msvc, rustdoc, test, clippy)22ec3d07

    There is no integration delta: the submitted branch already matches origin/main exactly at 22ec3d073. That main commit removed incomplete maintenance hooks and corrected receive-quarantine helper return values, restoring compilation and CI, but none of that work remains unique to this branch.

  • ›Main keeper: make CI on main green (public-api)e4301db1

    Updates the committed grit-lib API snapshot so CloneOutcome.repo appears before CloneOutcome.submodule_paths, matching the struct declaration and generated cargo-public-api output. This is a snapshot-only correction with no runtime behavior change.

  • ›Main keeper: make CI on main green (public-api)afe9a0b0

    There is no change left to integrate: the reviewed workspace and origin/main both point to 5cabe3d7e. The stale public API snapshot was already corrected on main by c81d42371, which only reordered the CloneOutcome::repo and CloneOutcome::submodule_paths entries in the generated API snapshot.

  • ›Main keeper: make CI on main green (public-api)31e3eeea

    Refreshes the committed grit-lib API inventory so its StageTrackedSummary fields appear in the order emitted by the pinned cargo-public-api tool. This is snapshot-only and has no runtime or API behavior change.

  • ›Main keeper: make CI on main green (public-api)dae3135f

    Updates one generated grit-lib API snapshot entry so `StageTrackedSummary` fields appear in the order emitted by the pinned cargo-public-api tool. There is no Rust source or behavior change.

  • ›Main keeper: make CI on main green (rustdoc)7902a376

    Qualifies the `line_change_counts` intra-doc link from `diff/core.rs` through the public `crate::diff` re-export. This removes the only broken rustdoc link reported by the failing main CI job without changing compiled behavior.

  • ›Main keeper: make CI on main green (rustdoc)7902a376

    This changes one rustdoc link in the diff line-count API to name its crate-qualified target explicitly. It has no runtime, public API, storage-format, protocol, or CLI behavior impact.

$ git log --oneline f7728c04..v0.5.24compare on GitHub ↗
02183994Fixes #2241: do not report mtime-only tracked refresh as staged
40e2209fFixes #2238: Git habit spellings and migration hints
f1ef4bdbFixes #2235: persist exec rebase state across interrupt
5cabe3d7Merge branch 'factory/rev-list-since-filter-stop-0e08'
e4301db1Refresh public-api.txt for ReflogDateUnparseable resolve error
ebc84ff4Merge branch 'factory/public-api-snapshot-5267'
f990553eReturn zero immediately for filtered count with --max-count=0
7da8304cFix filtered rev-list count limits and plain --count --since
b6ed056fFix rev-list --since frontier stop with filters and paths
1daa56cePreserve invalid reflog date errors through Repository::resolve
c40f88d9Fix reflog @{date} selectors to use relaxed approxidate grammar
c81d4237Refresh grit-lib public-api.txt for CloneOutcome field order
85502901Merge branch 'factory/skip-bundle-post-verify'
04e93356Fixes #2228: skip redundant post-index verify on bundle unbundle
6f396445Journal directory spool when restore replaces a directory with a file
89edd75bFix Ctrl-C during grit reset --hard and restore --source
e565cd5dMerge branch 'factory/rust-version-msrv-2117'
8533a4baMerge branch 'factory/clone-blobs-filter-sent-5241'
31069050Merge branch 'factory/reference-transaction-hook-9fb4'
b057a9f3Fix clone --blobs when server lacks filter capability
07f5bd68Merge branch 'factory/submodule-gitlink-2229'
ac95fe8fUpdate public-api snapshot for reference-transaction hooks
162c9baaMerge branch 'factory/icase-commit-path-5240'
ae2d2be5Restore zero_oid import in on_store tests
f58f8fe2Fix clippy after reference-transaction hook review fixes
9549b2aaFix reference-transaction hook null oid width and lock ordering
89e39883Fixes #2220: run reference-transaction hook on ref store updates
56b5f2bbFix #2229: uninitialized gitlink status and clone submodule note
4d05dc24Fix ignorecase collision losers on path commit and status
7ad686f5Fixes #2231: return WouldOverwrite for merge and untracked overwrites
b4bb1cf1Merge branch 'factory/worktree-post-checkout-hook'
b0315d78Fixes #2227: declare Rust 1.95 MSRV for published crates
68ebabf3Fix HTTP client rejecting credentials on 404 and other non-401 errors
6c0c1a52Fixes #2232: run post-checkout after worktree add
22ec3d07Fix grit-lib compile errors blocking CI on main
98ca92aaMerge branch 'factory/rebase-marker-labels'
e71150eadocs: upload-pack include-tag adds tags of any sent object
dabf88ccupload-pack: decide include-tag from the objects being packed
144da670Merge branch 'factory/restore-hunks-single-index-read'
e4f5b538Refresh grit-lib public-api snapshot for PackBitmapWriteOptions.reachability_roots
cbaaad54Refcount receive .keep markers and snapshot inventory keep status
59ef6e25Protect migrated receive packs with .keep until refs publish
3ab9a257Label rebase conflict markers with the replayed commit
bf8d147bFree xdiff alignment state before building compaction line bodies
64894066Filter stacked rebase replay by range descent
cf8ddf3aLimit stacked rebase replay to first-parent chains
6fe1f3c1Fix rebase update-refs for multi-commit stacked branches
f6018de1Record ORIG_HEAD for grit rebase --exec and -X
19a3b4c3Honor -X ours|theirs in resumable rebase planning
1885674dDocument ORIG_HEAD for reset, merge, rebase and pull
b0aa4d1fRecord ORIG_HEAD on reset, merge, rebase and pull
78cd7ed8Merge branch 'factory/ci-public-api-5190'
baf324d1Refresh grit-lib public-api snapshot for cargo-public-api ordering
d9d6a598Merge branch 'factory/unpack-memory-5179'
68558454Fixes #2216: bound unpack-objects memory with delta-base LRU
233def06Document when a worktree hunk restore writes the index
ed2632f4Skip the line diff for -G pairs where neither side matches
773bacceDocument pickaxe rename pairing and cover Pickaxe::matches_commit
ef28ed89Count literal -S matches with memmem
c96f591bPair renames before testing pickaxe -S/-G matches
6a3f33f2Fixes #2214: default grit index lock pid sidecars for stale-lock recovery
77fda0c2Merge branch 'factory/fix-status-unstaged-null-oid'
a1bb921aSkip redundant copies and line scans in the hunk diff pipeline
f5510d0aRead the index once when restoring hunks and write it only on change
3b583a06Merge branch 'factory/merge-commit-hooks'
4309196aFixes #2215: use null OID for unstaged worktree side on size drift
4de05aeaMerge branch 'factory/fix-gc-concurrent-2210'
45d5e045Document pre-merge-commit, prepare-commit-msg and pre-rebase
856bfc60Merge branch 'factory/sparse-oid-filter-5168'
4992b10aMerge branch 'factory/integrate-open-prs'
389cb7a6Hint how to finish a merge a hook refused
7ca8c07dTest commit, merge and rebase hooks against system git
407893f7Run pre-merge-commit, prepare-commit-msg and pre-rebase hooks
d4d6eed2Stream a local fetch's pack to a spool and skip per-object re-checks
4d4a1879Pack a local fetch's missing objects directly when it has haves
ac63bad9Link a source pack that holds a few objects the receiver already has
9518189bTell the pusher when the update hook declines a ref
11be8c12Don't trust a pack listing left by another store on the thread
79aa9d40Only report created paths as copies in detect_copies
e0b3e5f4Fix sparse:oid filter matching nested paths under included dirs
5de28347Pin migrated receive-pack packs until refs are published
843add09Record pre-repack pack inventory before maintenance roots
f85723efFix gc bitmap failure when pushes land during repack
8cbefdd2Merge branch 'factory/reset-undo-fast'
f90286c9Document the many-tag undo-commit numbers
42c0f8a3Test header-primed tip walks and the rewind shortcut
721c2333Read ref-tip commit headers in parallel for the orphaned-commit report
b4b486c5Fixes #2211: compact rewrite-apply journal for clean tree transitions
b89b9f54Hash gitlinks and type changes in patch-ids like git show
e8e3d00aName the branch whose replay conflicts in history rewrites
7cc16948Merge branch 'factory/switch-gitlink-advance'
4f53a9a1Merge branch 'factory/sigterm-sighup-clone-5156'
5931549dMerge branch 'factory/patch-id-empty-files'
43180738Advance the index gitlink when a submodule checkout drifted
b618e1e1Handle SIGTERM and SIGHUP like SIGINT for cancellable CLI operations
80f59af9Match git patch-id --stable for commits with header-only file entries
76ce3c28Document exit 128 for a refused pull --ff-only
48c01f68Exit 128 when pull --ff-only cannot fast-forward
b63df8b1Keep form feed and vertical tab in patch-id hashes
dd6822c3Slide diff hunks only across lines that compare equal
14e6e269Merge branch 'factory/ref-df-conflict-errors'
b334ea20Retry busy ref stores for core.filesRefLockTimeout
b5f88848Take the shared ref store lock last in linked worktree transactions
bb078536Report ref directory/file conflicts as name conflicts naming the blocking ref
2a2c86d7Document that one reflog expiry run reads each commit once
95a23e65Intern commits for the per-ref reflog reachability walk
a4760350Size the merged config entries once per load
02c335f4Evaluate hasconfig:remote.*.url: against the whole config load
c9b5a267Report overwritten branches on grit rebase --abort and explain a stale exec plan
e382eb35Restore a moved branch on rebase abort and refuse to resume a stale exec plan
e91d222fMerge branch 'factory/diff-worktree-binary-note'
464c969eReport worktree binary content changes in grit diff
004fca19Hash dirty files in parallel for every index-to-worktree diff
a90c1215Merge branch 'factory/dead-unused-lib-fns'
7da69722Share HTTP media-type parsing in non-Git-server detection
de44d281Record MIDX layer stamp reads in hot-path test metrics
e33eec00Remove grit-lib public functions that nothing calls
7af96b50Document the sparse-index collapse numbers
d928a9e0Test sparse-index collapse against Git's own sparse index
ad0360a0Collapse sparse directories in one pass on index write
620a96e1List only refs/ for an empty for_each_ref prefix on every ref store
029fb74aDocument initial ref transactions and the 200k-branch clone/fetch numbers
aad653c0Hand fetch's tracking-ref writes to the store without copying
14d2c508Cut per-ref filesystem work in files-store batches
da30adecWrite a clone's imported refs as one packed-refs file
52741684Check same-batch ref name conflicts in O(n log n)
6f42b82eDocument the sparse hard-reset numbers for changed excluded trees
41f7eaf1Keep changed excluded sparse directories opaque on hard reset
9a0e192fMerge pull request #2194 from schacon/loose-ref-listing-once
eb2680abMerge branch 'factory/reflog-expire-fast'
d6bd3c2eDocument how reflog expiry judges unreachable entries
545a19cfShare commit linkage across the refs of one reflog expiry pass
61d2f999Walk reflog reachability lazily from the total-expiry cutoff
125c3c3fExpire old reflog entries with a commit-only reachability walk
4bdb17bbSkip stat and readlink for regular loose ref files
f46206c8Render diffstats from the grit-lib model in the CLI
7a719e42Add a typed diffstat model to grit-lib
81a58c75Merge branch 'factory/add-skip-stored-blobs'
fdb0e3d9Document that packing refs keeps the refs directory
e4bf8f5dKeep .git/refs when packing refs empties the refs tree
86d3e5dbSkip writing staged blobs that are already packed or in an alternate
729e2d6bMerge pull request #2150 from schacon/worktree-prune-missing-gitfile
f0c26a9eMerge pull request #2149 from schacon/worktree-add-orphan
505df55cMerge branch 'factory/ref-oid-verification'
e5f18c9eCheck branch-commit tips on the files fast path
037889e9Check ref writes against the repository's own object store
a81ead5fRefuse ref updates to missing objects and branches to non-commits
2db83ea7Merge branch 'factory/merge-directory-renames'
e308c89cDelete conflict-only files on merge --abort
3275a31cDetect directory renames in three-way merges
04ad33d4Document where a push cancel lands
5e819fb1Stop a wire push on cancel while sending the pack or awaiting the report
a741643fPoll the cancel signal while a local push builds and indexes its pack
03fab2b6Document read_store, missing git dirs and hooks for injected stores
e9320c23Expose an Odb's whole lookup chain as one ObjectStore
9ee867d3Reject commit-graph octopus parents that have no EDGE chunk
735f0eeaGive custom object stores a commit-graph of their own
60a4cfceRead custom primary objects without the files pack context
890fd298grit blame: blame the working tree by default, add --contents
26ab16fbblame: attribute uncommitted worktree and buffer content
69d42f41Merge branch 'factory/undo-crlf-index-stat'
7d613fdbGive history undo's restored index entries real stat data
5f47a65eShare the per-path index stat refresh between restore paths
075d6bfdMerge branch 'factory/rename-default-50'
bb6f58cfMerge branch 'factory/revs-startup-lean'
b2a9e273bench: record short revision listings before and after
01d637abHex-encode object ids from a nibble table
3f1170daSnapshot the filter subprocess environment on first use
bcf6512frevs: build only the rows each output mode renders
1c526550Record the 2026-10-27 daily audit and deep-tree ancestor-check gains
7bdd239eGuard the ancestor-symlink probe count on deep chains
a29ea671Confirm deep ancestor chains with one openat2 walk on Linux
3ebc3443Make the ancestor-symlink cache per scan worker
51a68bafDetect renames at 50% similarity by default, matching git
687ebc54Merge branch 'factory/rebase-keep-same-tip-branches'
3dd9dbfcKeep other branches at the HEAD commit out of history rewrites
dc920890Merge branch 'factory/typed-hunk-select-errors'
43b62aa5Carry typed errors in hunk selection failures
b6ffcf1fHide every ancestor of excluded tips in --first-parent ranges
95781d56Lock in linear ignorecase staging on a clean tree with a regression test
00764209Document the Windows nanosecond stat default and its status numbers
444f57b1Compare index stat to the nanosecond by default on Windows
bd7cc293docs: record that show HEAD is bounded by startup cost
7aca5cefbench: publish daily numbers from the dist build
8b56f76ediff: walk tree entries borrowed from the raw tree payload
554e74caMerge branch 'factory/checkout-long-path-spool'
0024a125Merge branch 'factory/add-untracked-chunked-memory'
77a8a8ddKeep checkout spool names short for deep paths
3b8f4052Run receive hooks on local and file:// pushes
8c0e9b54Share the receive-hook runner in receive-pack
083ba62bGate bulk add peak memory against git add and record the numbers
a4ba01d9List root commit files from the index instead of a tree diff
cd5314eaStage untracked files chunk by chunk to bound add memory
540515d8Document rebase --exec commands that amend or add commits
ae38e043Thread progress and cancellation through rebase --exec recovery
34ec874cHarden rebase --exec recovery after amend conflicts
8d5a6cdfMove HEAD's branch when an exec amends a commit the plan kept
b0c3d304Test amending rebase --exec commands through the grit CLI
024a3569Adopt commits that rebase --exec commands amend or extend
3cc25842Merge branch 'factory/eol-status-size-shortcut'
289d1f26Let worktree remove succeed on a stale size with unchanged content
d11a911bRead a size-changed converted file once per status
69167f11Hash size-changed files in status when clean conversion is not the identity
cbf627dfRun the clone interrupt test on Windows
fd4ea972Release pack maps before removing a failed clone
3a9871f1Merge branch 'factory/push-create-cas'
efe98b48Report push creates lost to a racing writer as already_exists
ab6d7f44Compare-and-swap local push creates against an absent ref
7c3b6095Merge branch 'factory/ignore-dir-negation'
999ecd8eignore: let a later !dir/ re-include a directory a plain pattern matched
0732913cHash patch-id text hunks from the default diff engine
48a95bd9Merge branch 'factory/stage-type-flip'
bc30e45eDocument staging of file/directory type flips and the in-use hint
87f6e5e1Remove deleted tracked paths in one index compaction pass
8309b637Stage tracked paths that flipped between a file and a directory
4a167724Suggest closing programs only when a worktree file is actually in use
2074469cMerge branch 'factory/checkout-cancel-rollback'
788df824Test that an interrupted grit switch leaves the repository unchanged
52624553Let Ctrl+C cancel grit switch and clone on Windows
b69fb834Merge branch 'factory/drop-dead-diffstat-writer'
134553abRemove the unused Git-format diffstat writer from grit-lib
4ad55670bench: record log and walk rows before and after the walk read buffer
602e657blog: build only JSON rows for --json output
f0525e2aRead walked commits into one reusable buffer
536b8dfaUse the shared git, rev_parse and free_port helpers in tests
1f069dd1Add shared rev_parse and free_port test helpers
30718697Merge branch 'factory/stash-selection-report'
50aa4b41Report partial stash hunks from the stash push itself
7f52cc6aBreak cross-pack delta cycles when reusing stored deltas
afcdc084Refuse to write a full pack with a delta ahead of its base
338da46cResolve case-folded worktree paths only when the indexed path is missing
74d8bf80Keep detached auto-gc off the caller's output pipes on Windows
2e65faf2Explain conflicted restore and commit refusals
68fa9436Refuse staging conflict markers after the conflict stages were dropped
1ae305cbRefuse restoring only the index of an unmerged path
8dbc58e7Ignore SIGPIPE in every command that reaches a remote
490a5e19Store HTTPS credentials for any host and report rejected ones
126b20a7Report non-grit hosts as NotGritServer when signing in
176ebaaaMerge branch 'factory/patch-id-ranges-cherry-mark'
96719ff9Document that history undo puts consumed edits back
4416574fReport restored paths from grit history undo
03ff763dRestore consumed edits when undoing history fixup and absorb
c19afe01revs: reject non-symmetric ranges for --cherry-mark and --left-right with -n 0
e078e00erev_list: compute range patch-ids on worker threads
9dc98d35cli: patch-id ranges and revs --cherry-mark for reviewing rewritten branches
3af646e7rev_list: pair cherry-mark commits by patch-id
f53367a6patch_ids: expand revision ranges in Repository::patch_ids_for_revs
f99b8494Prune linked worktrees whose .git file is gone
976c9e95Allow adding worktrees to bare repositories
0898d215Add WorktreeBranch::Orphan for git worktree add --orphan