60 issue fixes · 11 other changes
v0.5.22 mostly fixes correctness gaps against system Git and cuts memory and time on large blobs, big worktrees and push, fetch and clone.
This adds a batch existence-query extension point to ObjectStore and changes cache-tree rebuilding to confirm reusable trees in batches. A one-file commit no longer probes every unchanged directory twice, while still rebuilding missing descendants and reporting missing blobs.
This change makes ISO-8859-1 working-tree encoding round-trip correctly: clean converts Latin-1 worktree bytes into UTF-8 blob bytes, while smudge converts representable UTF-8 back to Latin-1 and rejects unrepresentable text without data loss. It adds direct and system-Git compatibility coverage for clean, checkout, clone, aliases, and strict failure behavior.
This change makes checkout deletion failures visible and transactional: per-file removals are staged for rollback, failures preserve HEAD/index/worktree consistency, and the staging payload is spooled to disk instead of retained in RAM. The current implementation is not safe to integrate because its cross-filesystem spool path changes symlinks into regular files during rollback.
Remote receive-pack hook and diagnostic text now survives push sideband demultiplexing. Embedders receive ordered lines through `grit-lib::PushOutcome`, while the CLI emits them to non-TTY/TTY stderr for human output and includes them in JSON and Markdown.
Adds a durable `.git/grit/rewrite-apply` journal around history ref movement and worktree/index application. Interrupted operations can now finish or roll back safely, status exposes the state, commits and rebases recover or refuse unsafe progress, and legal newline-containing Git paths survive snapshot serialization.
Repository handles now carry repository-scoped cwd and environment into both long-running filter-process drivers and one-shot clean/smudge commands. Clone checkout therefore launches git-lfs inside the new worktree, while linked worktrees and Windows path forms receive the correct repository context too.
Small receive-pack inputs now inflate large non-delta blobs directly into chunked loose-object writes instead of materializing the payload in memory. Strict dot-special validation walks in-memory structural objects and reloads only blobs actually named `.gitmodules` or `.gitattributes`; malformed zlib streams and temporary-file failures also terminate cleanly.
This change makes Grit maintenance and cloning work with Git-created SHA-256 repositories. It excludes hash-width-independent null reflog IDs from reachability and synchronizes clone destinations to the advertised/source object format, reopening the post-fetch repository before checkout.
Protocol v2 upload-pack now advertises and handles server options, wait-for-done, and object-info. The round-2 correction also makes object-info distinguish an absent object from repository/read failures, so corruption and I/O failures are no longer silently represented as missing objects.
Loose-ref enumeration now skips an entry when it vanishes between readdir and metadata lookup, preventing unrelated concurrent ref deletion from aborting branch listing or server ref advertisement. A focused concurrent writer/lister unit test guards the behavior.
Fresh repositories now receive actionable, structured errors instead of leaked revision syntax: switch reports no previous branch, history commands explain an unborn branch, and empty stash pop reports no entries. Round 2 adds the missing public API snapshot entries, extends the fresh-repo regression to rebase, and documents the new error contracts.
Clean conversion now collapses expanded ident keywords before hashing or comparing worktree content. Ident-marked files also bypass the raw streaming identity path so status, diff, add, commit, and stash all see canonical repository bytes.
This change makes ignore-rule matching honor `core.ignorecase` using ASCII folding and closes the round-2 safety gap for tracked paths whose on-disk casing differs from the index. Status, clean, staging, auto-stage commit, and ignore checks are covered against system Git, including the case where an ignored directory contains a case-variant tracked file.
Index-only commits now distinguish a dirty but unstaged worktree from a genuinely clean tree. Library callers receive a typed error with unstaged/untracked counts, while the CLI emits stable `nothing_staged` or `nothing_to_commit` JSON kinds and actionable human guidance.
A successful reset now ends merge, pick, and revert state so later commits cannot accidentally retain a second parent or replay metadata. When the interrupted merge used autostash, reset stores that commit in the stash reflog without applying it, matching system Git.
This change makes absolute shallow fetch depth stay absolute across repeated fetches, keeps relative deepen behavior distinct, and makes shallow clones by lightweight or annotated tag stop at the requested commit depth. It also completes protocol-v0/stateless Smart-HTTP shallow negotiation so grit clients and servers agree on shallow and unshallow boundaries.
Blame now retains every merge parent long enough to find files renamed on the merged-in side, then hands matching lines to that parent under its original path. This fixes subtree merges and the mixed case where one parent has the result path while another has the content under an older name.
This adds low-level merge-driver resolution and execution to the shared tree-merge engine. Configured .gitattributes drivers, merge.default, built-in union/binary behavior, placeholders, conflict blobs, and add/add mode conflicts now produce the same trees and index stages as system Git.
History rewrites now consistently read and update HEAD, branches, and reflogs through Repository's injected RefStore. Post-apply HEAD is computed before the ref transaction, preventing a late HEAD lookup from returning an error after refs have already moved; exec and undo use the same store-aware path.
Repository opening now makes GIT_OBJECT_DIRECTORY the actual primary ODB for reads and writes, including relative paths and explicit empty-value failure. Fetch, push, pull, and bundle flows retain and use the caller's Repository/Odb instead of reopening .git/objects, while object-store consumers consistently ask the Odb for its directory.
Commit failures now distinguish a truly clean work tree from a staged-only commit attempt that leaves unstaged or untracked work. Human, JSON, and Markdown-facing error data carry accurate context and hints, including path-scoped commits that must not treat unrelated staged files as unstaged.
HTTP smart-discovery responses that look like HTML are now classified as a typed NotGitServer error instead of leaking a pkt-line parsing failure. The CLI renders a clear status/content-type message and reuses the existing check-the-URL next step.
Status now distinguishes a configured upstream from a local or unrelated comparison ref, suggesting `grit pull` only when pull would integrate the shown ref and `grit merge <ref>` otherwise. Fast-forward output names the current branch that actually moved, while retaining the merge source and adding an optional `updated_branch` JSON field; round two commits the corresponding snapshot.
Adds path-aware, typed errors for writes under repository metadata so commit and gc can identify the failing object, ref, reflog, index, or pack path and the CLI can return structured JSON guidance. The bare-gc and direct files-ref-store cases work, but detached linked-worktree commit still loses the path before reaching the newly fixed ref transaction.
Adds a CLI-only migration-hint layer that recognizes common Git spellings and points users to the corresponding grit command without exposing clap’s misleading pass-as-value tip. It also preserves the alias the user typed in usage text and distinguishes existing checkout paths from plausible branch names.
Local initial clones now install existing packfiles and loose objects directly, then compute transfer totals from pack indexes plus only the reachable loose tip. This keeps mixed packed/loose repositories on the local fast path while preserving the slower complete transfer path for alternates and other unsupported shapes.
The ignored-hook warning now gives two commands users can actually paste: a correctly quoted `chmod +x` command to enable the hook, and the accepted `grit config advice.ignoredHook false` command to silence the warning. The hook path is carried as typed library diagnostic data and rendered only by the CLI.
Routes push pack construction through a send-pack-specific boundary mode and preserves divergent-tip correctness by painting advertised-have ancestry through the repository commit cache. It also updates the public API snapshot and adds Git-backed pack tests, but the round-2 ancestry paint traverses the complete remote history before building each push pack.
Single-tag pushes now derive their summary name from `refs/tags/<name>` instead of falling back to the checked-out branch. Human and Markdown output show the tag, while JSON omits the legacy `branch` field for tag and other explicit refspec pushes.
Racily clean worktree files are now content-checked through an unconditionally parallel worker path when multiple workers and files are available. Matching checks are recorded on the index, and a persisted status refresh advances the index timestamp using the existing guarded helper.
Checkout deletions are now staged in a bounded temporary spool so a failed switch can restore already-removed tracked files. Cross-device moves preserve symlinks as symlinks, and the corrected regression proves rollback reaches the later permission failure rather than exiting early on a dangling link.
Locked rm/mv index updates now retain split-index serialization, including delete and replacement bitmaps, while preserving the existing flush-then-finish lock lifecycle. The round-two patch restores a silently skipped compatibility test and ensures an uncommitted flush changes permissions only on index.lock.
Adds typed reporting for case-insensitive checkout collisions, corrects clone file/byte summaries to reflect actual worktree slots, and prevents default commit auto-stage from rewriting collision-loser index entries. Human clone output warns on stderr while JSON and Markdown expose stable collision paths.
Reflog-writing porcelain APIs now accept validated Signature values instead of caller-formatted identity strings. Backend-independent transaction validation rejects newline/control injection even when callers use RefStore::prepare directly, and reset preserves its best-effort identity behavior for unconfigured users.
This change makes repository-metadata permission failures identify the affected path instead of returning a bare OS error, including commits from detached linked worktrees. It also normalizes displayed repository paths across Windows, normal worktrees, linked worktrees, and bare repositories.
This change streams apply-ready unified patches for `grit show --patch-text` and `grit log --patch-text`, including binary bodies, instead of the human side-by-side renderer. The round-2 update forwards resolved pathspecs, supports first-parent merge patches, and places show decorations before streamed commit output, but source-side rename filtering remains incorrect.
Large tracked binary files can now be re-hashed as raw streams even when core.autocrlf is enabled and an index entry exists. The round-two patch also restores metadata-aware symlink handling in the serial index/worktree diff path and adds direct regressions for both cases.
Path-scoped commits now accept ignored files that are already tracked or force-added without accidentally committing staged content outside the selected pathspec. The follow-up fixes preserve parent-tree content for negative pathspecs and support ignored directory pathspecs while avoiding duplicate ignore validation.
This change routes high-volume hard reset, stash apply, and clean merge materialization through the optimized checkout/index paths. The round-two corrections ensure stash reconciliation owns the final index write and hard reset preserves the index timestamp invariant needed for reliable racy-file detection.
Automatic gc now requests Windows job breakaway and treats a denied breakaway as a failed background launch. It falls back to synchronous maintenance with truthful output, eliminating the prior retry that could remain trapped in a restrictive job while claiming to run in the background.
This change turns held index and loose-ref lock files into a typed library error with optional PID/liveness information, then exposes that data consistently through Grit's human, Markdown, and JSON error outputs. The round-2 amendment ensures this remains true when the typed library error is nested under another error and anyhow context.
Local clones now hard-link eligible loose objects before packing the remaining objects, avoiding large in-memory repacks. Human `grit cat` streams blobs of at least 1 MiB and resolves replacement objects through documented library APIs whose signatures are committed in the public API snapshot.
Merge source resolution now accepts branches, tags, commit IDs, ORIG_HEAD, and revision expressions through a typed grit-lib API. Its range guard correctly handles legal ref names containing bare braces, preventing three-dot input from being resolved to a merge base and accidentally committed.
Auto-staging keeps unrelated case-collision groups separate even when their 32-bit hashes collide. The branch also reuses staging collision metadata, restores direct path lookup, and makes the dentry regression select the correct fixture on case-insensitive storage.
Send-pack now ends revision painting as soon as no queued commit remains reachable only from the WANT side, instead of draining advertised HAVE history to its roots. A test-only ODB-read counter locks in the bounded walk on deep history.
Streams large object payloads through cat, status/add hashing, restaging, pack serialization, restore-related reads, and local clone paths instead of retaining multiple whole-blob copies. Local clones preferentially hard-link loose objects, while JSON deliberately omits large blob bytes and exposes that choice in the schema.
Branch and annotated-tag headers now stay ahead of streamed patches when output is piped or redirected. Log patch pages retain their continuation hint, while existing path filtering and merge behavior stay covered.
Embedding developers can build and browse grit-lib's public API docs again. The patch replaces two unresolved links with public item paths and describes relative deepen without linking to a private helper.
Rejects malformed caller-supplied reset identities before HEAD, the index, worktree, reflogs, or operation state can change. Preview and dry-run share the check, including current-HEAD targets and disabled reflog updates.
Local fetch stops reopening ref/config state for thousands of tracking refs and tags. It skips preparing object-copy exclusions on an up-to-date fetch while preserving pruning, ref classification, FETCH_HEAD, and remote HEAD maintenance.
Wide content merges retain fewer duplicate paths, index records, parsed trees and checkout plans. Independent quiet measurements show 25–35% lower peak RSS and 1.7–3.1% faster median merges; results remain compatible with system Git.
Keeps native argument bytes intact until parsing, so unsupported textual operands produce usage errors instead of panics. Adds an early repository-selection guard that prevents raw-byte directory names from redirecting commands to a similarly named Unicode repository.
Bounded branch containment now preserves matches and truncation when grafts or replacement refs create ancestry cycles. Ordinary histories retain the fast memoized path; rewritten histories use the existing cycle-aware batch walk and ignore stale commit-graphs.
The first switch after a no-checkout clone now writes the complete requested tree and creates the index, including when selecting the current HEAD or a same-tree branch. It keeps the real previous HEAD for hooks and reflogs, protects colliding files, and retains staged deletions represented by an existing empty index.
Local unshallow now creates historical release tags during the first fetch, including annotated tag chains. It computes the requested history before choosing automatic tags, then copies missing tag objects and retains the existing availability safeguard.
Blame accepts reversible paths reported by status and preserves exact filename bytes in attribution. The review follow-up also prevents ./grit+pathhex:6162 and equivalent cwd-relative operands from silently selecting the file ab.
Patch IDs now include nearby unchanged context while excluding the rest of the file. The exact ten-line example produces Git's expected 5ab484ff6e8f56d89403fae49e2063b0712ed58e through both the library-backed command and the normal rendered patch.
Hard reset now gathers the missing content it will need and downloads it together before materializing files. My pinned Cargo reproduction falls from 139.5 seconds to 1.00 second, with one data fetch instead of 2,022.
Blame now compares complete borrowed line records, so adding or removing the final LF correctly assigns the last line to the modifying commit. Output content and line numbers stay unchanged, and explicit whitespace ignoring still passes through newline-only changes.
Git can now read the branch and commit selected by grit when adding or switching a reftable worktree. Symbolic and detached HEAD values are committed to the worktree's own reftable stack; loose HEAD files remain discovery placeholders, and compaction no longer imports stale loose state.
Pack index checksum mismatches and unresolved pack deltas now have structured library error variants instead of requiring callers to parse text. Fsck uses the typed checksum error, thin-pack detection uses the typed unresolved-delta error, and optional index loading no longer carries an unreachable exact-string exception.
Status models now directly report whether the worktree is clean, group conflicts by typed kind, and return typed next-step guidance. The CLI’s human and Markdown renderers consume those results instead of independently re-deriving the same policy, with existing output preserved.
This removes dead library and CLI surfaces plus unused test helpers, reducing the branch by 376 net lines and eliminating three empty integration-test binaries. It also turns two previously orphaned unit-test functions back into real tests and keeps bundle failure code 128 while mapping it to the correct typed status.
Both target-comparison entry points now share one library implementation. The required API still returns its existing error when discovery finds no target; the optional API still returns None.
Path-limited history scans tree data directly for requested paths, reuses packed delta bases, and stops merge comparisons after finding the parent it follows. My target run fell from 1.58 seconds to 0.29 seconds, with 12.3% less peak memory; sparse history also keeps merges selected by Git.
Patch parsing shares file initialization and the binary/hunk body loop across Git and traditional headers. Public signatures and parsed results stay unchanged; production code shrinks by 26 lines, and the benchmark now measures a valid patch and checks success.
Rust callers can now parse reflog references and newest-first entry selectors through a documented library API with distinct syntax and integer errors. The CLI calls that API and preserves its existing human, JSON, and Markdown error output.
Dense tracked-directory scans reuse an open parent and a filename buffer on 64-bit Linux; status also reuses its existing configuration snapshot. Independent clean 10k status improved 14.8%, from 19.105 to 16.276 ms, beating system Git's 17.882 ms; RSS changed little.
Ignore queries now reuse the worktree root already resolved by Repository instead of resolving it again for every path. The existing fallback still handles alternate filesystem spellings, and the added tests protect boundaries, missing paths, trailing slashes, Unicode, symlinks, and bare-repository errors.
Restores checkout parallelism while retaining cancellation and bounded blob preparation. On this VM, all six reported regression scenarios improve by 11–35%; clone checkout drops from 4.16 to 2.91 seconds against system Git at 5.07 seconds. Public APIs, CLI outputs, repository formats and wire behavior are unchanged.
Embedding callers can now ask Repository::revision_label for branch or tag metadata, including raw annotations, without depending on the CLI. Show delegates that work to the library and preserves its human, JSON, and Markdown output.