What’s New in Git 2.41
Git 2.41 is out, bringing along contributions from more than 95 developers, including 29 first-time contributors. The release focuses on improving repository housekeeping and refining some core internals. Here are the most significant changes.
Unreachable Objects and Cruft Packs
Every Git repository is built on objects. Blobs hold file content, trees represent directories and their contents, and commits reference trees along with parent commits. An object is considered reachable if you can trace from a branch or tag through history to reach it. Objects that can’t be reached this way are unreachable. Over time, Git removes unreachable objects to keep repositories compact, typically during git gc or automatic garbage collection runs.
Because deleting objects from a live repository carries risk, Git doesn’t remove unreachable objects immediately. Instead, it enforces a delay: an object must be unreachable and have remained unwritten since a specified cutoff point before it’s eligible for deletion. For example, running git gc --prune=2.weeks.ago would:
- Bundle all reachable objects into a single pack.
- Keep unreachable objects that were written within the last two weeks in separate storage.
- Discard all unreachable objects older than that.
Historically, Git tracked the age of unreachable objects by writing them as loose files and using file modification timestamps as proxies. But this approach creates problems when there are many unreachable objects: loose files consume inodes and can inflate repository size. Git 2.37 introduced cruft packs to address this, storing unreachable objects together in a packfile while using a sidecar *.mtimes file to record each object’s last-write time. This method reduces inode usage and allows unreachable objects to be delta-compressed.
In Git 2.41, cruft pack generation is now the default. Running git gc will automatically produce a cruft pack, meaning you get the storage and resource benefits without any extra configuration.
Faster, More Reliable Reference Transactions
Beyond garbage collection, Git 2.41 brings internal refinements to how references (branches and tags) are updated. These changes improve the speed and consistency of reference updates, particularly in repositories with large numbers of refs or when multiple processes are updating refs concurrently. The improvements are transparent for users but matter for those running large-scale repository operations.
The release also includes several bug fixes and small optimizations across areas like protocol negotiation, worktree handling, and error-message clarity. While not headline features, these tweaks contribute to a smoother, more predictable Git experience.
Reverse indexes now written by default
After upgrading to Git 2.41, you may notice new *.rev files appearing in your repository's .git/objects/pack directory. These on-disk reverse indexes store the same data Git previously built in memory on demand: a mapping from each object's position in packfile order to its position when sorted by object ID (OID).
Git needs both orderings constantly. A normal pack index (*.idx) lets Git binary-search for an object by name and find where its data lives in the pack. The reverse index answers the opposite question—given a position in the pack, what object is there? Previously, this was computed at runtime by materializing a list of pairs for every object, consuming time and memory. In Git 2.31, this structure began to be written to disk alongside the pack as a *.rev file.
With Git 2.41, the on-disk format is enabled by default. After running git gc once, you may notice faster operations, particularly ones that frequently convert between object orders. Measurements from the change that flipped the default found a 1.49x speed-up in the CPU-heavy portion of a git push (tested on the last 30 commits of torvalds/linux). Far more dramatic gains appear in trivial lookups: computing an object's on-disk size with git cat-file --batch='%(objectsize:disk)' ran nearly 77x faster.
Richer credential-helper protocol
Git's credential helper mechanism lets you store passwords and tokens in tools like Keychain.app or libsecret. However, the protocol traditionally only conveyed enough information for password-based authentication. Services wanting to use OAuth had to pass bearer tokens through basic authorization, and there was no way to communicate the additional context a server sends in a WWW-Authenticate header—such as OAuth scopes—to the helper.
Git 2.41 extends the credential helper protocol so that WWW-Authenticate headers can be forwarded to credential helpers. This enables helpers to see the full set of authentication requirements from the service, opening the door to more fine-grained (scoped) access to repositories.
One-walk ahead/behind in for-each-ref
Listing how many commits a branch is ahead of or behind another usually required two reachability queries:
git rev-list --count main..my-featuregit rev-list --count my-feature..main
That works, but comparing many branches against a common base means repeatedly walking the same commits. Git 2.41 introduces %(ahead-behind:<base>), a formatting atom for for-each-ref that computes the relationship for each branch in a single traversal.
For a set of unmerged topic branches, the old approach required a script loop:
$ git for-each-ref --format='%(refname:short)' --no-merged=origin/HEAD \
refs/heads/tb |
while read ref
do
ahead="$(git rev-list --count origin/HEAD..$ref)"
behind="$(git rev-list --count $ref..origin/HEAD)"
printf "%s %d %d\n" "$ref" "$ahead" "$behind"
done | column -t
tb/cruft-extra-tips 2 96
tb/for-each-ref--exclude 16 96
tb/roaring-bitmaps 47 3
That took over 500 milliseconds. A single for-each-ref call using the new atom produces the identical output in 28 milliseconds—more than a 17x improvement—with far less scripting:
$ git for-each-ref --no-merged=origin/HEAD \
--format='%(refname:short) %(ahead-behind:origin/HEAD)' \
refs/heads/tb/ | column -t
tb/cruft-extra-tips 2 96
tb/for-each-ref--exclude 16 96
tb/roaring-bitmaps 47 3
[...]
Machine-readable fetch output
The default human-friendly output of git fetch summarizes updated references, but it shortens names, omits full before/after values, and columnates text, all of which complicate scripting. Git 2.41 adds --porcelain for this command. The new output format presents four space-separated fields per line, making it straightforward to parse with scripts.
+ 4aaf690730..8cebd90810 my-feature -> origin/my-feature (forced update)
<flag> <old-object-id> <new-object-id> <local-reference>
Narrowing connectivity checks on fetch
Git 2.39 improved push-side connectivity checks by letting servers ignore hidden refs that weren't advertised to the pusher. Git 2.41 applies similar logic on the client side with the new fetch.hideRefs configuration.
When a client fetches, its connectivity check terminates at refs from any remote, not just the one being fetched. For repositories with many remotes—especially on resource-constrained machines—the check can take an unbounded amount of time. With fetch.hideRefs you can hide everything except the remote tracking refs for the particular remote you're pulling from:
$ git -c fetch.hideRefs=refs -c fetch.hideRefs=!refs/remotes/$remote \
fetch $remote
That configuration works because transfer.hideRefs values beginning with ! re-expose refs that were previously hidden (applied in reverse order). In a repository with many remote-tracking branches, a no-op fetch on a constrained system dropped from 20 minutes to roughly 30 seconds with this setting in place.
Deeper corruption checks with git fsck
Besides verifying that objects are present and connected, git fsck looks for subtler problems—out-of-order trees, missing author fields in commits, or malicious .gitattributes and .gitmodules files. Git 2.41 expands this coverage to reachability bitmaps and on-disk reverse indexes.
For both, fsck detects and warns about incorrect trailing checksums, which indicate the underlying data has been corrupted. When examining *.rev files, it also checks that the values inside match what's expected. The full list of checks is documented under fsck.<msg-id> configuration in the git fsck documentation.
Under the hood: replacing `git gc` in background
For most users, `git gc` is an occasional maintenance command. But for large repositories or scripts that run it frequently, the process can block concurrent operations because it holds a lock on the entire object database. Git 2.41 introduces a replacement: git maintenance can now run a garbage collection pass that avoids exclusive locking by operating on object alternates, a mechanism typically used to share objects across multiple repositories.
This new mode, enabled with git maintenance start --background, splits the object store so that existing refs and objects stay in the main object database while new objects are written to a separate one. The garbage collector can then prune the now-quiet main store without contending with writers. When the pass finishes, the two stores are merged back together with a final lock, which is far shorter than a full git gc.
The risk and the tradeoff
While the window for corruption shrinks dramatically, it is not zero. The main risk, as detailed in the release notes, is a concurrent writer creating an object that references an unreachable object that git gc is in the middle of removing. For example, a push that depends on an object slated for deletion could corrupt the repository if the new object lands after the old one was pruned.
Because of that edge case, this background mode is not enabled by default. You must opt in explicitly. Additionally, Git's documentation recommends this mode for repositories where concurrent writes are a concern; single-user repos can safely stick with the regular git gc.
The whole shebang
That's a sample of the changes in Git 2.41. For the full list, see the release notes for 2.41, or check the release notes for any previous version in the Git repository.
If you'd like to dig deeper into the raciness behind the new git maintenance mode, this section on object deletion from GitHub's engineering blog provides the background.



