Cruft Pack Housekeeping Gets More Flexible
Git 2.50 arrives with a host of improvements for git repack, particularly around the management of unreachable objects. Since Git 2.43 introduced support for multiple cruft packs, users have been able to split unreachable objects across several packs using the --max-cruft-size option. However, the behavior of that flag has been a consistent source of confusion and bugs.
The core issue was that --max-cruft-size did double duty: it was supposed to govern when to split or combine packs, but it also imposed a hard limit on the size of any single output pack, much like --max-pack-size does for regular packs. This led to frustrating scenarios. For instance, running git repack --max-cruft-size=200M on a repo with two 100MiB cruft packs would refuse to merge them into a single 200MiB pack, since the output would technically exceed a size limit derived from the same flag. Worse, it could cause Git to wastefully rewrite the same packs repeatedly.
Git 2.50 resolves this by introducing a new flag, --combine-cruft-below-size, which cleanly separates the two concerns. This new option dictates which *existing* cruft packs are eligible to be combined, rather than limiting the size of the resulting pack. This is particularly useful for repositories that have accumulated a large number of cruft packs over time—you can now gradually consolidate them without fighting the pack-size constraint. The original --max-cruft-size flag has been repurposed to act strictly as a cruft-pack-specific override for --max-pack-size, controlling only the size of the outgoing packfile.
The update also fixes a subtle but significant bug related to object "freshening." In certain cases, unreachable objects stored across multiple cruft packs were not having their modification times updated when rewritten. This meant they could be garbage-collected prematurely, before their intended expiration window. The fix in Git 2.50 ensures that these objects are properly freshened, keeping their retention schedules intact.
Reachability Bitmaps Go Incremental
Git 2.50 also marks a major step forward for large repositories by bringing multi-pack reachability bitmaps to the incremental multi-pack index (MIDX) format. This builds on work from Git 2.47, which introduced incremental MIDX chains to alleviate the overhead of rewriting a massive index file on every update.
To understand the significance, it helps to recall why multi-pack bitmaps exist. Standard single-pack bitmaps store a set of bits for specific commits, where each bit position maps to an object in that single pack. While fast, this approach is inefficient when objects are spread across dozens of packfiles. Multi-pack bitmaps solve this by mapping bit positions to objects across an entire MIDX, allowing a single bitmap to cover a broader repository state. This provides substantial performance wins for object reachability queries.
The catch was that updating an MIDX meant rewriting the entire index and its associated bitmap, which became prohibitive for very large repositories. The incremental MIDX format solved the index issue by allowing layers to be appended, but bitmap support lagged behind. With Git 2.50, that gap is closed.
Now, each layer in an incremental MIDX chain can have its own *.bitmap file. These layers work together, letting Git answer reachability questions for commits at any point in the chain without recalculating the entire repository graph from scratch. For enormous, fast-moving repositories, this means new commits can be integrated with efficient, incremental bitmap updates regardless of the total repo size.
That said, this remains a highly experimental feature. Support for repacking objects into incremental MIDX layers with bitmaps is still quite bare-bones. The Git maintainers are actively developing it, so expect further refinements—and likely new caveats—as the implementation matures.
ORT becomes the one and only merge engine
Git 2.50 marks the end of an era for the recursive merge engine, which has been completely removed from the codebase. The ORT engine — introduced in Git 2.33 as a from-scratch rewrite — has now fully taken over after serving as the default since Git 2.34.
One notable capability that arrived with ORT is the ability to check whether two things are mergeable without writing any objects to the repository. Previously, git merge-tree --write-tree would persist new objects generated during the merge attempt, which could accumulate over time. Git 2.50 adds a --quiet mode to merge-tree that relies on the exit code alone, making the same determination without creating any new objects.
As a wise man once told me, “Deleted code is debugged code!”
Elijah Newren, the author of ORT, marked the removal of recursive with that sentiment. For those interested in the internals, Newren published a five-part series on ORT's development and optimization.
Object inspection gets a filtering upgrade
Scripts that work with repository objects often rely on git cat-file in its various modes. Enumerating all objects of a specific type has typically required piping the output through filters. For trees, you might previously have written something like:
$ git cat-file --batch-check='%(objecttype) %(objectname)' \
--buffer <in | perl -ne 'print "$1\n" if /^tree ([0-9a-f]+)/'
Git 2.50 brings the object filtering mechanism from partial clones to git cat-file, so the same operation becomes more concise:
$ git cat-file --batch-check='%(objectname)' --filter='object:type=tree' <in
Housekeeping for maintenance and reflogs
The git maintenance command gains three new task types this release. worktree-prune removes stale worktree metadata, rerere-gc expires old entries from recorded merge conflict resolutions, and reflog-expire clears stale unreachable objects from the reflog. Each mirrors functionality already available through git gc.
The existing loose-objects task also becomes more configurable. The task packs loose objects that have been missed by earlier garbage collection passes; the pack size was previously fixed at 50,000 objects, but the new maintenance.loose-objects.batchSize configuration lets you adjust that limit.
Cleaning up a branch's reflog history has also been simplified. Previously, dropping an entire reflog required git reflog expire $BRANCH --expire=all. Git 2.50 introduces a dedicated sub-command, so the same operation is now git reflog drop $BRANCH.
Elsewhere, the --allow-unknown-type option for git cat-file has been effectively retired. The option dated back to a feature that allowed git hash-object to write objects with arbitrary types beyond blob, tree, commit, and tag. The feature saw very little use, and git cat-file -p --allow-unknown-type couldn't even print those objects' contents. In Git 2.50, the option silently does nothing, and support for writing unknown-type objects has been removed entirely.
Reference handling optimizations
A set of low-level improvements target how Git processes references. The git update-ref command no longer spends time checking whether a proposed refname could also be a valid object ID — a look-up that made names ambiguous. Since update-ref is meant for plumbing, dropping this check yields performance gains for higher-level commands that depend on it.
Git also now caches whether any prefix of a proposed reference name already exists — important because you cannot create refs/heads/foo/bar/baz if refs/heads/foo/bar or refs/heads/foo is already present. Previously, each prefix check required creating a new reference iterator. The reference backends in Git 2.50 can "seek" existing iterators instead, reusing the same one across all prefix checks.
HTTP tuning, Perl reduction, and cosmetic rebase changes
For complex networking environments, Git 2.50 adds three new configuration options to control TCP Keepalive behavior through curl: http.keepAliveIdle, http.keepAliveInterval, and http.keepAliveCount. These complement the existing http.lowSpeedLimit and http.lowSpeedTime options, provided your operating system supports the underlying probing.
Git's dependency on Perl continues to shrink. This version removes Perl as a requirement for the test suite and documentation toolchain. Many Perl one-liners from tests were rewritten as shell functions, builtins, or small C programs. The few remaining tests with hard Perl dependencies will be skipped on systems without a working Perl.
The TODO script shown by git rebase -i receives a cosmetic update. Commit messages listed there are purely informational — editing them doesn't change anything, since you'd use the reword command for that purpose. To make this clearer, messages in the TODO script are now prefixed with a # comment character:
pick c108101daa # foo pick d2a0730acf # bar pick e5291f9231 # baz
Sparse checkout and bundle-uri improvements
Interactive staging gets friendlier in sparse checkouts. Both git add -p and git add -i now work without expanding the sparse index, following the ongoing effort to make more index-touching commands compatible with sparse repos. You can stage parts of changes without waiting for Git to populate the sparsified portions of the index.
The bundle-uri feature — which lets a server direct clients to download a *.bundle file before performing a fill-in fetch — becomes noticeably faster. When requesting the remaining objects from the server, Git previously advertised only the branches under refs/heads/* that it picked up from the bundle. Git 2.50 advertises all known references from the bundle during the fill-in fetch, avoiding redundant data transfer.
The rest of the iceberg
These are just the highlights. Full details are in the release notes for 2.50, with previous versions also available in the Git repository.



