60 issue fixes · 19 other changes
v0.5.23 mostly cuts time and memory in history walks, diff, push, fetch and status, and fixes many push, rebase, checkout and library-embedding bugs.
Corrects a stale injected-store test to expect the current-branch error already promised by the library. Adds refusal/state-preservation coverage across ref backends and verifies that both checked-out error variants retain the same CLI JSON kind.
Clean status now shares one validated MIDX backing within its owning repository cache and releases checksum-only pages. On the exact git/git fixture, median peak RSS fell from 61,728 to 16,616 KiB and elapsed time from 35.99 to 21.64 ms, with no-MIDX human timing unchanged.
Keeps already available content readable when a partial clone is maintained offline. Marked packs remain separate from newly packed local objects, and ordinary packs holding the only available copies of promised objects now survive cleanup with their sidecars.
Completed rebase calls now return the HEAD OIDs observed before and after that invocation. Both rebase CLI entry points use that typed result for JSON and Markdown, so a successful continuation no longer hides a real HEAD movement behind null.
Rename and copy fingerprints now aggregate byte weights once per blob and compare sorted tables without allocating a hash map for each candidate. Scores and all eight measured command outputs remain unchanged; my runs show roughly 7× faster tag name-status and 8.7× faster detection across 1,000 inexact renames.
Large clean worktrees containing directory, file, or dangling symlinks now produce clean parallel diffs and correctly reject empty commit previews. Retargeted links use their target bytes; regular files retain parallel hashing.
Blame now preserves each pending line's source filename when unchanged edge lines cross a rename-and-edit. Commit, original line, and original path can therefore navigate to an existing source location, including after a subtree move.
A successful rebase exec that modifies tracked files now stops immediately with exec_dirty and preserves its index and worktree. Both rebase entry points explain recovery; continue/skip resume the saved command position after edits are saved, and abort explicitly restores the original state.
Wire pushes now explicitly request a single ref transaction when atomic mode is selected. Unsupported servers return a typed error before update commands or objects are written, including dry-run and no-op requests.
This change makes orphan detection stop near the commits being reset instead of traversing deep history kept alive by old tags. It also deduplicates and cheaply peels reset hide refs, then adds regression coverage, a daily benchmark, and matching documentation.
Tree diffs release completed sibling metadata within the existing ODB cache budget. Metadata-only file output also reuses its input strings, cutting both library and CLI memory while preserving the public API and output.
Reset and switch now keep unchanged excluded directories opaque, making a sparse monorepo transition cost roughly the visible working set. The review follow-up prevents stale cleanup paths from deleting external files through replacement symlinks or failing after a directory becomes a regular file.
Packed-object reads now retain at most 16 sparse-read files per pack store, so the 96-pack repository remains fully readable under a 64-descriptor limit. The full change also packs imported clone tags and reduces loose-tag bounded-log RSS from 46,436 to 26,076 KiB, without changing public APIs or repository formats.
Stash apply/pop now preserves exact Git filename bytes, including literal grit+pathhex: names and Unix non-UTF-8 paths. Local-overwrite checks protect the real file, and clean applies, deletions, moved HEAD, conflicts and --index produce the same state as system Git. Independent release checks retain roughly 45–46% lower peak RSS and 49–50% lower elapsed time than current main.
Retain flate2 and add the tests and benchmark infrastructure needed to evaluate another decoder reliably. This change ships no Sirocco dependency, production behavior change, public API change, or format change.
Ranged blame now releases inactive pack readers instead of retaining a file for every historical pack. The 300-pack case succeeds under a 256-descriptor limit, and the exact three-line git/git request uses 1.58 times system Git's memory.
Local-path pushes now enforce the same receive-side ref policies as wire receive-pack, rejecting prohibited non-fast-forwards, deletes, and updates to a checked-out branch. The round-2 fix makes ancestry validation see objects from both sides before a dry run and the populated remote store after a real copy, while tracking the two shared helpers in the public API snapshot.
Windows worktree validation now rejects NTFS alternate streams and illegal characters, trailing spaces or dots, and reserved DOS device names before checkout can create or alias files. The round-2 rewrite expresses those rules as independent per-piece checks and preserves typed library errors.
Keeps automatic GNU installs on the existing stock binary after same-source measurements showed the optional RELR link slows short dist commands. It still publishes a separately named x86_64 GNU RELR artifact for manual use, guarded by PIE, RELRO, BIND_NOW, ABI, oldest-loader, startup, and ASLR checks, and records reproducible benchmark evidence.
Unfiltered revision counts with exclusions now use a generation-ordered two-sided paint when a usable commit-graph is present. The walk stops once the included frontier is resolved instead of constructing complete include and exclude ancestor closures; CLI behavior and public signatures do not change.
Mutating checkout-family operations now reserve the index before they can rewrite worktree bytes or create stash objects. The held lock is threaded through checkout/index persistence, so a foreign index.lock returns LockHeld with the repository byte-for-byte unchanged; stash entries remain reachable if a later reset step fails.
This change prevents Windows worktree checkout from creating reserved device names, trailing-dot/space names, and other Win32-invalid components, while extending protectNTFS to reject GIT~1 aliases with stripped suffixes on every platform. The review revision adds a cheap alias prefilter so ordinary checkout paths no longer pay the prior regression.
Adds a per-operation cache for case-insensitive directory spelling resolution, so each parent directory is listed once rather than once per indexed file. Add/commit staging and collision winner accounting share that cache while retaining existing spelling selection behavior.
Push now executes the repository's pre-push policy gate after remote refs are known and before commands or objects are sent, across local and wire transports, with `--no-verify` as an explicit bypass. Relative `core.hooksPath` lookup is anchored to the worktree root (or git directory for bare repositories), so invocation from a subdirectory no longer silently skips hooks.
This change makes a one-commit network push scale with the objects being sent instead of the remote's total advertised branch set. It limits thin-delta candidates to actual history-edge commits, caches peer tree blobs once, avoids broad pack-index/bitmap scans, and buffers git:// and SSH reads.
Adds `RepositoryOptions::from_process()` as the discoverable, explicit way for embedders to open user repositories with the same process-derived system/global configuration as grit and Git. Updates user-repository examples and guides to use it, while preserving `empty()` for hermetic repositories, and adds a system-config CRLF status regression against system Git.
Repository handles now expose ordinary drive-letter or UNC paths on Windows when dropping the verbatim prefix preserves the path’s meaning. Unsafe-to-simplify paths remain verbatim, and all internal canonicalization sites use the same spelling so comparisons remain consistent.
Local and file:// fetch, push, and clone now transfer only objects reachable from the requested refs, preventing hidden refs and deleted/private work from leaking through shared packs. Plain filesystem-path clone deliberately retains Git's fast local behavior, while exact source packs can still be hard-linked without rebuilding.
Revision-range counting paints included and excluded ancestry once, then counts left-only commits with O(1) membership checks instead of launching fresh ancestry walks for every candidate. Filtered, first-parent, and left/right counts share the faster path, including a correction for hidden commits reached through non-first parents.
This change removes the index-size-multiplied cost of staging under case-insensitive/macOS path settings. It caches compact directory listings, prefetches parent directories in parallel, avoids unnecessary Unicode and pathspec work, and keeps case-only rename handling compatible with system Git.
Receive-pack now honors repository fsck settings before quarantined objects are migrated, rejecting malformed commits, trees, and tags without updating refs or polluting the main object database. Round 2 also ensures an accepted missing-committer diagnostic cannot hide a later NUL-in-commit error.
Adds a linkable `write_merge_tree` API that materializes a three-way merge into the repository object database and returns the stored tree together with structured conflict and stage data. The existing prediction path remains non-persistent, while docs and the compiled merge guide now explain and exercise the distinction.
Blame now lets xdiff-style compaction place changed groups across raw common-prefix/suffix boundaries instead of freezing those lines first. It keeps that correctness affordable by diffing only the reachable window, while preserving whole-file occurrence counts and Git’s no-context common-tail cut.
Default-order `A...B` walks now stop once both sides reach every merge base instead of materializing full ancestor closures. The same work also corrects criss-cross ranges by excluding all merge bases and improves merge-base traversal when only stale commits remain.
This change prevents a tree merge from silently choosing one file when two logical files land at the same destination. It detects the collision before the ordinary path walk, content-merges source-side edits into renamed entries, and records both sides as the established add/add conflict shape.
Linked worktrees opened over a custom or in-memory RefStore now use their own admin-directory HEAD and HEAD reflog. Commits, detach, and switch operations therefore affect the linked worktree's branch/state instead of silently moving the main worktree's branch, and embedders gain a public API to reopen linked worktrees on shared injected stores.
A tracked file that cannot be opened during an index-to-worktree scan is now conservatively reported as modified instead of aborting the entire status operation. Switch consequently uses its existing local-change protection, while operations that require bytes—staging that path and producing a patch—continue to fail explicitly.
Repository open now validates every injected object store against the repository’s configured object format and returns a typed error before callers can use an incompatible ODB. Matching custom stores and ordinary files-backed SHA-256 repositories continue to open and write correctly.
Remote operations now honor the RefStore attached to an open Repository instead of silently reopening the git directory for local tracking refs. Custom backends receive fetched and pushed tracking tips, prune deletions, remote HEAD updates, and inspect reads without creating a competing `.git/refs/remotes` tree.
Root commits encountered while scanning the onto side for already-applied patches are now diffed against an empty tree instead of treating the all-zero object ID as a real tree. This allows rebasing over histories that merged an unrelated root while preserving skip-applied behavior.
Repository reflog reads, queries, deletes, expiry, and gc expiry now use the RefStore attached to the open Repository rather than reopening on-disk refs and logs. This fixes embedders using MemoryRefStore or another injected backend, while keeping files and reftable rewrites compatible with system Git.
Checkout application now journals every worktree mutation and rolls it back when cancellation or another error occurs, including additions, overwrites, per-file and wholesale deletions, gitlinks, sparse paths, and hard-reset early unlinks. If restoration is incomplete, callers receive a typed error with the original cause and unrestored paths; `grit switch` also converts its first SIGINT into cooperative cancellation so rollback runs.
Adds an opt-in, consistently serializable grit-lib result surface for front ends. It replaces string modes and raw identity headers with typed FileMode and Signature values, serializes errors and operation outcomes, and includes paused status/conflict state without changing grit CLI JSON output.
Protocol-v2 upload-pack now ends negotiation as soon as acknowledged haves cover every commit want. It emits `ready`, a raw section delimiter, and the pack in one response, while preserving the existing `done` path and honoring `wait-for-done`.
Receive-pack now recognizes a shallow client's boundary packets, exempts those boundary parents during strict loose-object ingest, and decides per ref whether accepting the push would create a new receiver-side shallow root. Ordinary depth-1 CI pushes onto a complete server now work over pipe and smart HTTP; genuinely history-truncating pushes remain refused unless `receive.shallowUpdate` is enabled.
This change removes avoidable copies from metadata-only tree diffs. Rename detection now returns already ordered modification-only lists directly, the CLI converts entries to output rows in one pass, and parsed tree reads no longer retain a redundant raw-object cache copy.
Cache-tree validation now treats an object reachable through any configured ODB layer as reusable, rather than requiring it in the writable primary. A files-backed batched probe checks loaded pack indexes before loose paths, preserving packed-repository performance while the regression tests cover repeated commits and stash.
Interrupted rewrite recovery now reconstructs the pre-checkout index/worktree state before completing a checkout, so partially written files are not mistaken for user edits. Status returns success, preserves the rewritten tip when completion works, safely rolls back when it does not, and exposes finished/rolled-back/pending outcomes to embedders and CLI users.
Incremental upload-pack now derives a small set of client-held tree/blob bases from the have boundary and feeds them into the normal delta search as external-only candidates, producing compact REF_DELTA entries. It also bounds the have-side commit walk and computes bitmap reachability once, eliminating the pathological per-blob whole-bitmap scans.
This change closes a thin-pack correctness hole by refusing to use a blob already scheduled for transmission as an external REF_DELTA base. It adds a deterministic planner regression and a system-Git round trip proving `index-pack --fix-thin`, object readback, and strict fsck succeed.
Push pack construction now writes directly from a shared source-pack mapping into either the connection or a temporary bundle file. Large smart-HTTP request bodies cross a bounded producer/consumer channel into ureq’s chunked POST instead of accumulating in one or two full-size vectors.
This consolidates conflict reporting around `grit_lib::operation::{Conflict, ConflictKind}`. Status, merge, pull, sequenced pick/revert, rebase/history predictions, and stash apply now expose the same path-plus-kind shape, and all affected CLI outputs render the same direction-aware names.
Long-running staging, commit, stash, merge, replay, restore, and rebase library sessions now expose consistent progress and cancellation plumbing. The round-2 corrections specifically make conflict-stop publication safe for rebase and cover all stash-push reset modes, including hunk selection, pathspec, and staged-only paths.
Library callers now receive structured stale/unknown hunk and patch-apply failures from scoped commits instead of losing them as prose. Empty path-scoped commits also have a dedicated error variant, while rendered human messages remain unchanged.
`grit push --delete` is now a mode flag whose positional refs are all planned as deletions, so no requested name is silently discarded. Mixed `src:dst` refspecs and empty delete requests fail with typed planning errors before any ref update is sent, while JSON reports one result per requested deletion.
Automatic maintenance now delta-compresses reachable loose objects using the same streaming pack writer and pack tuning as a full repack. It keeps the incremental behavior: the new pack is self-contained, existing packs are untouched, no bitmap is written, and unreachable loose objects remain loose.
Ctrl-C now requests cooperative clone cancellation instead of deleting the destination from a signal handler while clone workers are still writing. Once copy/download/checkout work has stopped and checkout rollback has completed, the CLI removes the partial destination and returns exit 130, allowing the identical clone command to be retried.
Resumed rebases now mirror the completed branch move into HEAD’s reflog before HEAD is reattached. Continue and skip therefore leave HEAD@{0} consistent with HEAD, while a reflog-write failure keeps the operation detached and abortable; skip also records the correct message.
Fetch matching now records whether a tag row came from automatic tag following or from a user refspec. The post-transfer retention pass prunes only automatic rows, so explicit annotated-tag destinations are created and forced refspecs can move release tags without changing normal tag-follow semantics.
Skipped commits now have two distinct destinations: the upstream equivalent remains visible in rewrite reports, while replay chaining and ref movement continue from the rewritten predecessor. This prevents earlier or later topic commits and stacked branch refs from falling back onto an old upstream ancestor.
Deletes three unused internal I/O helpers and their dead-code allowances. Keeps the existing path-display assertion, exercising the formatter used by production ref-store errors.
Filtered author/message counts scan commits with a reusable buffer instead of retaining commit maps. The round-two correction reads excluded parent links from the commit-graph, avoiding decompression of roughly 49,500 excluded bodies for a 500-commit range.
Fsck reports now offer a borrowed, typed summary containing each finding's severity and visibility plus overall integrity and warning status. The CLI uses those results, removing its own policy helpers while preserving output and exit status.
Pack-index lookup now predicts a row from the hash prefix, brackets the candidate with growing steps, and finishes with binary search using full object IDs. It reduces resident mapped pages on short logs without changing public APIs, output, or Git file formats.
Changed large files are hashed and stored once, scoped commits avoid staging a throwaway index, and push selection avoids repeated scans of queued commits. Daily plans now execute real work and honor requested sample counts.
Symmetric revision ranges reuse parent links and timestamps already parsed while resolving endpoints such as HEAD~500. The change seeds the existing repository cache up to 1,024 entries, with guards for histories whose traversal parents differ from stored parents.
Branch rename now validates packed refs without allocating a map of unrelated entries or preparing unused replacement text. Independent measurements reproduce a 36–38% speedup and about 12% less peak memory, with compatible repository files and flat or better related timings.
Log statistics avoid a second full patch and count only the changed line region using borrowed text. My release measurements halve the target's time and reduce its peak memory, with no material timing regression in 18 other scenarios.
Diff target selection now lives in grit-lib and is shared by the CLI's normal and patch-text paths. The follow-up makes invalid revisions produce the same revision error whether path limits come from argv or a file.
Branch inventory shares one configuration snapshot and the existing ref store, while full packed-ref scans avoid an intermediate hash table. My release builds reproduce 7.5x faster listing of 6,667 packed local branches and 9.0x at 99,999 local branches; the daily headline moves from 1.832 to 1.743 ms. No measured latency or peak-RSS regression exceeds 5% across 17 scenarios.
Share immutable captured environment variables when repository/config setup clones an Environment, while keeping typed overrides independent. Fresh controlled measurements reproduce a small speed improvement. This review follow-up adds a repeatable build procedure and corrects the benchmark evidence; it makes no further production-code change.
Patch headers and attribute patterns now share one decoder for quoted paths. Each caller retains its existing validation and malformed-input behavior.
This replaces duplicated, numeric-state, manually indexed trailer handling with shared typed helpers and iterator-based scans. It keeps the public API and CLI unchanged, removes 333 production-library lines, and adds broad comparisons against system Git for trailer placement, config, sign-off, and cherry-pick message rewriting.
This change reduces bounded author/committer/message-filtered log work in two places: it compiles byte-oriented matchers without the expensive Unicode-dot cache/CPU probe, and it reuses commit bytes already read during traversal for filtering and rendering. It keeps the optimization inside grit-lib and adds unit, Git round-trip, storage-format, and Criterion coverage.
Makes paged log continuation tokens repository-local and compact while keeping ordinary human/Markdown log reads non-mutating. It also removes the redundant “change” label from display and makes Unicode graph lanes the default, with explicit ASCII fallback.
This resolves each distinct branch tip once during merged/contains filtering and reuses that commit ID throughout the reachability walk. It also avoids duplicate loose-ref/reflog scans in main worktrees and snapshots the CLI process environment once after global arguments are applied.
Repository discovery failures now carry a structured `NotARepositoryReason` rather than an opaque string. Callers can distinguish absence, malformed/special gitfiles, and read failures without matching display text, while the CLI-visible wording remains unchanged.
Batch note-removal previews now compose every requested removal against one in-memory notes tree and return the exact commit id that the real batch write produces. The CLI consumes that typed plan directly, and markdown dry-runs clearly use future-tense wording.
Removes 17 unused public `grit-lib` items plus three unused `grit-test-support` helpers, mostly remnants of the retired compatibility CLI and dropped commands. It also removes `terminal_size` from grit-lib and simplifies the test pack builder now that its unused omit-trailer mode is gone.