75 issue fixes · 11 other changes
v0.5.21 mostly cuts memory and time in clone, fetch, gc/repack, fsck and merge on large repos, and fixes many concurrency, Windows and Git-compatibility bugs.
Delete-heavy switch and hard reset now avoid redundant per-file metadata/content work. Hard reset delegates tree-diff deletions to wholesale directory removal, scans unchanged entries once, verifies only dirty/racy candidates in parallel, and preserves index stats from path-based records; switch reuses batched scan stats and hashes racy files in parallel.
This change turns upload-pack for already-packed repositories into a bounded streaming pipeline. It lazily reuses packed whole objects and deltas, frames bytes directly onto side-band output, and makes object enumeration and offset lookup cheap enough for large full clones.
Adds invocation-scoped repository targeting and config overrides to `grit`, plus consistent quiet handling for clone, fetch, pull, and push. The implementation is library-first: typed config overrides and repository-path builders live in `grit-lib`, while the CLI parses and applies them before dispatch.
Adds a fallible object-existence API and migrates correctness-sensitive callers so custom-store outages remain typed errors instead of being misclassified as missing objects. The round-2 revision makes lookup ordering strict and consistent with object reads, while preserving the old lenient advisory API.
This makes custom reference backends practical by exposing shared transaction semantics and a reusable conformance suite, while aligning memory and reftable behavior. It also lets repositories open with injected object/ref stores without a fake .git skeleton and fixes relative ODB paths. The round-3 fixes specifically make linked-worktree and log-only symref reflogs correct.
Adds gc auto-skip behavior and a pack-reuse rewrite path intended to avoid rebuilding deltas for already-packed repositories. It also repairs trusted index-row generation, handles excluded delta bases safely, disk-stages streamed packs, and keeps explicit repack from auto-skipping.
Required clean/smudge filters can no longer silently store or compare raw bytes when their command fails. Shell commands now use Git-aware interpreter resolution, including Git for Windows' bundled shell when only the cmd directory is on PATH, while optional filter failures retain Git's raw-content fallback.
HTTP authentication now asks credential helpers with the repository-level protocol and host, adding the repository path only when credential.useHttpPath applies. This lets host-scoped credential-store/keychain entries authenticate clone, fetch, and push while retaining URL-scoped credential configuration matching.
Path-scoped commits now take index.lock before reading repository state and retain it until the branch and real index are safely updated. Commit messages remain in memory unless a commit-msg hook actually runs, and the commit uses one HEAD snapshot for both its parent/tree basis and the final compare-and-swap.
Bundle creation now streams a pack while reusing existing SHA-1 pack entries and deltas, avoiding inflated-object retention and a second full output buffer. The follow-up correctly keeps SHA-256 and loose-only repositories on windowed delta search, and bundle header selection now preserves annotated tag objects across commit prerequisites.
Adds a public cancellation model and threads it through fetch, bundle ingestion, status, push, and related call sites, with typed cancellation errors and focused regressions. Round 5 attempts to extend cancellation into parallel pack indexing, but live progress cancellation and post-index ingest cancellation remain ineffective.
Partial commits now reserve the repository index before doing visible work, then write the final index through that same lock after the branch update. Held locks produce a typed library error and stable CLI JSON for path-, hunk-, and patch-file-scoped commits, so retries are safe and no stale MM index state is left behind.
Unlimited default-order ranges now use the hide-frontier traversal even without a limit, avoiding full-history include/exclude closure materialization. Follow-up fixes preserve Git-compatible handling of unreadable history behind exclusions and honor commit-graph opt-out, with cursor compatibility and system-Git regressions retained.
Repository pathspec matching now evaluates `:(attr:...)` against the repository's real attribute stack, including user-defined and builtin macros and case-insensitive patterns when configured. It also validates malformed attr clauses up front and exposes reusable matching through `PathspecMatcher`, so diff filtering loads attributes once per pass.
Fetch tag following now classifies delivered tag targets through pack membership instead of walking and inflating the fetched object graph. Unadvertised annotated tags are peeled only through locally stored tag objects, preventing partial-clone promisor fetches while preserving Git-compatible followed-tag sets.
Hook execution no longer loses output produced by earlier hooks when a later hook cannot spawn. The accumulated bytes travel in HookError::Spawn and are exposed consistently to embedders and CLI human/Markdown rendering, with typed tests covering the complete failure path.
Populated-tree checkout chunks now mutate only the in-memory index. After the final chunk, grit builds the TREE cache extension once and writes the index once, avoiding a full commit-tree walk and discarded cache tree for every 2,048-file chunk.
Index-mutating add operations now acquire index.lock before reading the index and write the updated index through that same lock. Concurrent writers can no longer report success and then overwrite one another; related rm and tracked-staging paths also preserve concurrent updates.
Adds a docs CI guard that treats every written `grit_lib::…` path and hand-written docs.rs grit-lib URL as a public-API claim, resolving it against local rustdoc. It also rewrites stale library-guide paths to their canonical public locations and documents the public no-op progress sink.
Introduces one library policy for translating worktree modes into index modes, then uses it throughout staging and worktree comparison. A symlink represented by a regular file on a core.symlinks=false checkout now remains mode 120000, stays clean when untouched, and records edited target text as a symlink blob.
Sparse-aware checkout updates now keep paths outside the configured cone out of the worktree and preserve their index representation across reset, switch, merge, restore, pull/rebase, and sparse-index collapse. The round-2 amendment additionally allows switch and merge to proceed when an exact skipped added path already has an untracked file, leaving that file untouched and reporting it as a tracked modification like Git.
Grit now recognizes Git-started rebases across status, switch, and rebase session handling. It prevents branch switching while an operation is paused, reports the rebased branch correctly, safely aborts a foreign rebase including rerere cleanup, and writes enough Git-compatible paused state for Git to continue a grit-started rebase.
Filtered upload-pack now preserves every annotated tag in a peel chain and applies partial-clone filters directly during pack object enumeration. This removes the expensive rev-list/reopen/hex round trip, fixes filtered fetches with haves, and avoids loading peer blobs when a blobless pack cannot use them.
Moves protocol-v2 ls-refs prefix selection ahead of ref enumeration and object peeling, so a narrow request scans only matching refs. It also reuses packed-ref peel records, avoids object reads when peel was not requested, and adds compatibility/performance coverage for both v2 and v0 advertisements.
Merge now resolves its source through a documented grit-lib API instead of a CLI-only branch lookup. It accepts local and remote branches, lightweight and annotated tags, full or abbreviated object IDs, pseudo-refs such as ORIG_HEAD, and expressions such as tag~1, while retaining structured errors for scripts.
This replaces sentinel branch strings and duplicated stash surfaces with typed outcomes, moves live history message helpers out of the misleading porcelain::rebase module, and gives porcelain mutations typed Signature identities resolved through Repository. The round-two amendment closes the corruption hole by validating rewrite committers before any object write and tightens Signature parsing.
Introduces a typed remote-unreadable error from the library through the CLI, giving clone, fetch, pull, push, and remote refs stable JSON classification and exit 128. It also tightens local repository validation and makes linked-worktree and separate-git-dir checkouts usable as local remotes.
Received packs are still spooled to disk, but indexing now bounds mmap residency, reads random delta data with positioned I/O, streams index output, and completes thin packs in place. This removes pack-size-scaled RSS while also correcting thin-pack pack/index checksum naming.
Upload-pack now resolves deepen-not ref names and annotated tags, emits the required wanted-refs section for v2 ref-in-want fetches, and advertises only enabled protocol-v2 features. The round-2 amendments reject duplicate want-ref lines and unconfigured sideband-all requests before any packfile is sent.
Pack installation now waits until thin-pack completion before choosing the pack checksum, index trailer, and final filename. Regression coverage exercises both network fetch and receive-pack, proving repositories remain readable by stock Git.
This makes index-only removal preserve edited working files when the index still matches HEAD, while continuing to reject the one unsafe case where staged content differs from both HEAD and disk. It also replaces a string-only refusal with per-path typed reasons and restricts worktree comparison to relevant removal paths.
Shared-index rebuilds now write a real checksum even when the main index is configured to skip its checksum, and derive the shared filename/link OID directly from that trailer. The same change replaces a quadratic shared-entry position scan with binary search and adds Git round-trip coverage for commit, write, reuse, zero-link, and corruption paths.
This adds advisory post-operation hook dispatch to the library and wires switch, merge, amend, rebase, history rewrites, and pull through it. The round-two correction preserves the rebase onto commit explicitly so post-checkout receives Git's old-tip→onto arguments, including conflict stops and pull rebases.
Pack fsck now resolves delta trees depth-first and inspects each inflated object immediately, retaining only compact connectivity facts instead of all object bytes and tree names. The existing index-pack resolver becomes a shared internal walker, pack input is memory-mapped, and the benchmark suite now rejects large fsck RSS regressions.
When commit auto-staging makes an AD or reverted-MM index state cancel back to HEAD, grit now writes that cleaned index before returning NothingToCommit. Dry-run remains non-mutating, and successful commit behavior is unchanged.
Switch now detects commits that would be left reachable only through reflogs when moving away from a detached HEAD. The library returns bounded typed summaries, while human, JSON, and Markdown CLI output warns users and gives a recovery branch command; exactness is preserved under skewed commit dates.
This change makes staging untracked trees linear in directory count. Add now requests one full untracked listing in the common case, while the remaining collapsed-directory cache path walks each collapsed subtree once and applies the original pathspec filter.
Push, pull, clone, merge continuation, and worktree/branch switching now preserve typed failure data through grit-lib and render concrete next steps in all three CLI output modes. URLs no longer expose smart-HTTP discovery suffixes, dirty/conflicted paths become structured fields, and command-specific exit statuses survive error wrapping.
Unifies file↔symlink changes under TypeChanged across tree, index, worktree, status, stash, and checkout-guard diff paths. The round-2 revision specifically prevents WorktreeVsHead from reporting an intermediate staged index mode when the actual worktree has changed again, so its status and modes now describe HEAD versus the final filesystem state.
Repacking now copies existing packed objects and deltas into a streamed output pack by default, while explicit --force performs a bounded streaming delta search. The round-2 correction preserves an OFS delta even when its base row in the source pack is unclaimed because an older pack supplied the selected copy.
Loose-object writers no longer share a temp filename when publishing identical content. All loose write paths now create exclusive pid/counter temp files, stream or copy compressed bytes into them, and converge successfully when another writer has already published the destination.
No-argument fetch and pull now resolve the checked-out branch’s configured remote instead of silently contacting origin. The round-2 revision handles local-tracking branches explicitly: fetch retains the prior useful behavior of contacting origin, while pull integrates the local branch without a network transfer.
Non-compaction multi-pack-index writes now default to format v1, while callers can explicitly request v2 and repository maintenance honors `midx.version`. Version selection is centralized and validated before dry-run returns, preventing both incompatible default files and invalid dry-run requests from silently succeeding.
This change makes pack bitmap generation inspect packed entry headers, parse commits once, and walk trees by entry mode without inflating blobs. It also reuses unwritten fork-point bitmaps and performs closure checks during the bitmap walk, preserving Git-readable output while removing nearly all bitmap-writer time on a cargo-sized pack.
RevWalk::walk now treats a top-level `A...B` as a symmetric difference instead of silently walking the wrong history. A shared parser distinguishes that operator from literal ellipses in `:/message`, `^{/message}`, and `rev:path`, and unsupported one-string walk inputs now return typed errors.
Adds automatic repository maintenance after successful object-writing commands, using typed grit-lib decisions and a thin CLI launcher. Round 2 makes loose-object auto-repack preserve Git semantics by packing only reachable objects, pruning expired unreachable objects, and correctly detaching/matching gc processes on Windows.
Large regular files can now be staged without loading or copying their full contents in memory. The library streams identity-clean files into Git-compatible single-object packs and safely publishes checksum-named pack/index pairs even when writers race or resume an interrupted install.
This change makes commit-graph writes detect repositories whose effective parent lists differ from commit objects and skip safely instead of producing a graph Git rejects. It also removes stale monolithic and split graphs so a subsequent gc repairs repositories affected by older Grit versions, and reports the typed reason through the library and all CLI output modes.
Plain restore now recognizes matched unmerged index entries and returns a typed, structured conflict error before changing any files. Conflicted stash pop/apply and dirty switch failures now tell users how to proceed, while the CLI remains thin over library behavior.
The tree merge now recognizes when both sides rename one base file to different names, preserves Git's stage-1/2/3 index layout, and reports a typed rename_rename conflict instead of silently committing duplicate files. Merge checking, real merge, pick, rebase, human output, JSON, and resolution documentation all use the corrected result.
Tree merges now create one lazy attribute-rule context for the entire operation, load the current index at most once, and resolve each path's attributes once before applying union/binary/content behavior. A regression test, wide content-merge benchmark, committed baselines, matrix registration, and benchmark documentation accompany the hot-path fix.
Auto-stage commit now reuses the index stat data produced by its staging pass instead of walking every tracked file again after creating the commit. The change is confined to grit-lib behavior plus regression coverage, rustdoc, and a recorded benchmark baseline.
Windows hooks now receive ordinary drive/UNC repository paths instead of verbatim `\\?\` paths, so Git inside a hook reads grit's actual index. Shell hooks resolved from Git for Windows also receive that installation's MinGW and MSYS tool directories first on PATH, restoring grep, sed, env, and similar tools.
Composite object reads now remember the first damaged-copy error while continuing through later backends. A later healthy replica still wins, but if every remaining backend misses or is damaged, callers receive the first corruption error rather than a false “object missing” result.
Worktree file failures now carry a typed repository-relative path and operation, producing actionable in-use diagnostics rather than anonymous or duplicated OS text. Checkout, staging, diff, batched reads, stash, and CLI structured errors use that context, while failed stash cleanup removes the newly created stash tip.
Unknown options in `grit commit` can no longer be consumed as the positional commit message and silently commit the worktree. The command now also supports `-q/--quiet` for successful human output while retaining JSON and Markdown output.
This change makes every maintenance and integrity history walk honor shallow boundaries, including from linked worktrees. It centralizes the shared `shallow` file path through `commondir`, then threads the resulting boundary set through repack, gc/prune, fsck, commit/revision walks, and bitmap generation.
Received pack streams can now be expanded directly into any writable object store, including full, OFS-delta, REF-delta, thin, SHA-1, and SHA-256 packs. Repository-level received-pack ingestion and bundle unbundling automatically use this path for custom primaries, while files-backed repositories retain pack installation.
This replaces pack-objects’ eager all-payload planner with the shared streaming delta search used by repack. Upload-pack, push, bundle, and local clone now search blobs, trees, and commits lazily, preserve reusable packed deltas, emit bases first, and produce packs close to Git’s size while sharply reducing memory.
Adds non-interactive askpass credentials to the library HTTP transport after credential helpers cannot complete a username/password. It supports GIT_ASKPASS, core.askPass, and SSH_ASKPASS, safely carries URL userinfo into credentials, wires repository/process environments through the default HTTP factory, and documents the behavior.
Large blobs can now move from loose or whole packed objects to work-tree files without materializing the full payload in memory. The path also avoids redundant conversion/hash copies, validates corrupt streams before installing files, and preserves selective ODB backend error propagation.
Repository-scoped revision queries now see injected ref backends instead of silently reopening an empty files backend. Linked worktree add/list/remove likewise use injected refs and objects, including repositories whose git directory is outside the work tree, while files/reftable repositories retain their existing disk-backed path.
Binary files in `grit diff --patch-text` now carry complete `GIT binary patch` bodies and full content ids instead of header-only entries. The library resolves each side lazily from caller bytes, the ODB, or normalized worktree content, while the CLI only selects the source.
Streams human and Markdown commit-range patches through a bounded ordered worker pipeline instead of retaining every rendered file diff. It also streams patch-text chunks in order, preserves change detection for metadata-only/streamed outcomes, and keeps Markdown output byte-equivalent to collected rendering.
Smart-HTTP fetches can now request objects that were reachable when refs were advertised but ceased being tips before the stateless POST. The round-3 correction also handles moved annotated tags whose targets are reachable commits, trees, or blobs instead of assuming every peeled target is a commit.
Annotated signed tags now show only their human subject in tag listings, even when signature armor immediately follows the message. The full stored annotation remains available unchanged.
Adds cancellation propagation through clone checkout, local object copy, switch, reset, history rewrite, and checkout execution; adds opt-in serialization for porcelain-facing models. Round 3 chunks parallel file deletion and adds a backward hard-reset regression, but one populated wholesale directory can still be fully deleted before cancellation is observed.
Adds a common `is_cancelled()` query to grit-lib's top-level and operation-specific error types, so embedders can detect user cancellation without matching each wrapper shape. It also publicly exposes the existing `RemoteResult<T>` alias and documents the intended remote error-query pattern.
Wire pushes now choose the same configured delta-capable pack modes as local pushes instead of assuming that a small number of ref tips means a small object closure. Initial pushes build full delta-compressed packs; incremental pushes remain thin and honor the server's OFS_DELTA capability.
This replaces upload-pack's eager peer object closure with compact negotiation boundaries and bitmap-backed peer reachability, then limits thin-pack base search to name-hash candidates and have-tip trees. A second fix paints commits reachable from haves so divergent tips stop at shared history without restoring the multi-gigabyte object closure.
This makes first status after clone, switch, or hard reset verify racily clean files in parallel instead of opening and hashing them serially. It also removes the redundant per-file lstat by applying the stat data already gathered by the worktree scan, while retaining the same whole-second racy rule and one-time index rewrite.
Clone now reserves a new destination atomically before repository initialization, so concurrent clones cannot all race into the same directory or delete the eventual winner. It preserves the established error precedence for already non-empty destinations while ensuring invalid options do not create artifacts.
Makes local object copying and pack installation safe when multiple fetch processes publish the same content-addressed files. Writers use unique staging and no-clobber publication; losing a race is success, and failed/cancelled installers never remove files another successful installer may already depend on.
Shallow upload-pack requests now peel annotated tag wants to commits before calculating depth, so emitted shallow borders are always commits. Include-tag selection now obeys both send-side and client-side shallow grafts, preventing tags below the cut from pulling full history into a shallow pack.
Conflicting `pull --rebase` now enters the same resumable rebase session as direct rebase, leaving markers, unmerged index entries, and operation state for continue/abort/skip. Its human, JSON, and Markdown results identify the paused operation and stopped commit; critically, `pull --check` now reports only the prediction and does not claim an operation is in progress.
Normal reads of sorted `packed-refs` now binary-search the mapped bytes instead of decoding and indexing every record at open time. Unsorted files remain lazy linear scans, while `grit fsck` detects a header that falsely claims sorted order; compatibility and malformed-input tests cover the new behavior.
Large files-backend ref deletion batches now prune packed refs in one rewrite and persist each update without repeatedly rebuilding the whole batch state. If a later commit-time recheck fails, records for the failed and unreached refs are merged back under the held packed-refs lock, preserving exact ref and peel records.
This removes four recurring costs from `grit show` and other object-reading hot paths: unused human/Markdown ref decoration scans, one-item thread-policy/pool setup, per-read empty-tree hashing, and unnecessary config-path canonicalization. It adds focused regression tests and committed before/after benchmark evidence without changing public APIs, CLI output, or Git formats.
This change removes repeated config/attribute loading, redundant worktree diffs, whole-index scans, unnecessary blob inflation and serial file writes from stash push/pop. It also fixes deferred-write directory pruning and ensures internal stash commits remain unsigned, matching Git.
Replay outcomes now carry subjects and change IDs needed by pick/revert callers, including records reconstructed after a stopped sequence. The CLI maps those typed outcomes directly instead of re-reading commit objects, and pick/revert share one empty-policy argument type.
This change eliminates duplicate commit reads while building log pages and trims fixed ref, rev-parse, and pack-index lookup costs. It also corrects Linux benchmark timing so sub-millisecond improvements are measured at child exit rather than rounded to the next RSS polling tick, and records paired benchmark evidence.
This change adds one typed Repository::head_commit API and replaces repeated HEAD-to-commit matching across history operations and tag handling. It also centralizes Error-to-HistoryError conversion and fixes rebase exec restoration so injected and on-disk ref sources cannot produce a mismatched HEAD/index/worktree state.
Mixed reset now reuses valid cache-tree subtrees and clean index stat rows instead of rereading every tree and hashing every tracked file. Hard reset uses the same tree walk and writes the resulting TREE extension, while the shared seeded-index path also benefits partial commit and stash.
This change removes redundant reads from tracked staging and paged log walks, bounds and parallelizes large tracked restaging, and makes protocol-v2 have negotiation lazy. It also records reproducible benchmark evidence and adds system-Git compatibility coverage for chunked add -u.
Deletes four unused public methods on the repository-scoped diff handle and the private implementation chain reachable only through them. It also removes the never-produced merge `set_upstream` outcome from CLI rendering, v2 schema, and documentation; pull retains its separate, live upstream-adoption result.
Protocol-v2 fetch negotiation now walks local history lazily in growing rounds instead of inflating the whole history before the first request. It also handles replayed ACKs without falsely resetting the in-vain counter and seeds negotiation from remote-tracking refs, with protocol tests, benchmark evidence, and documentation included.