Git 2.49 arrives with better delta compression
Git 2.49 is out, bringing improvements from more than 89 contributors, two dozen of whom are new to the project. The release’s headline change is a reworked packing strategy that can dramatically shrink both the time needed to repack a repository and the size of the resulting packfile.
Git’s object storage has two modes: loose objects stored individually, and packfiles that group many objects together. Packfiles have two advantages over loose storage. Object lookups are faster because a single read can pull many objects from the packed file, whereas loose objects require a series of system calls just to locate and open a single object. More importantly, packfiles let Git store objects as deltas — instead of saving two nearly identical versions of a large blob in full, Git can store the changes from one version to the next, greatly reducing overall storage.
Finding good delta candidates is the tricky part. Git’s traditional heuristic has been the “name hash,” a numeric hash of a file’s path that weights the final 16 non-whitespace characters most heavily. That scheme, dating back to a 2006 commit from Linus Torvalds, groups files by extension and handles simple renames well, such as a/foo.txt becoming b/foo.txt.
But it has a blind spot: repositories with many same-named files at different paths, such as several CHANGELOG.md files for different subsystems, tend to compress poorly because the hash treats them as highly similar even when their contents barely overlap.
Git 2.49 introduces a second-generation name hash that takes more of the directory structure into account. Each level of the path hierarchy gets its own hash value, which is bit-shifted and XORed into the overall hash. The result is a function that is sensitive to the full path rather than just its tail. That has a measurable impact on real-world repositories: repacking microsoft/fluentui improved from roughly 96 seconds to 34 seconds, with the pack shrinking from 439 MiB to 160 MiB.
The new hash is not yet compatible with Git’s reachability bitmaps, but it is available now in the latest release. You can opt into it by passing the new --name-hash-version flag to either git repack or git pack-objects.
Backfilling Blobs and Cleaning Up Partial Clones
If you work with partial clones using --filter=blob:none, you may have hit an unpleasant surprise when asking for the history of a file:
$ git blame README.md
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (1/1), 1.64 KiB | 8.10 MiB/s, done.
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (1/1), 1.64 KiB | 7.30 MiB/s, done.
[...]
The problem is that a --filter=blob:none clone keeps all commits, trees, and annotated tags, but only the blobs reachable from HEAD. When you run a command like git blame, Git needs every historical version of the file to compute diffs at each layer. Without those objects locally, it fetches them from the remote one at a time — creating a large number of tiny packfiles and hurting both storage and performance.
Git 2.49 introduces git backfill to address this. The command uses the new path-walk API to group missing blobs by path, batching the requests so the server can send a small number of packs with much better delta compression than fetching each blob individually. After running git backfill, the earlier blame operation looks like this:
$ git clone --sparse --filter=blob:none [email protected]:git/git.git[...] # downloads historical commits/trees/tags
$ cd git
$ git sparse-checkout add builtin
[...] # downloads current contents of builtin/
$ git backfill --sparse
[...] # backfills historical contents of builtin/
$ git blame -- builtin/backfill.c
85127bcdeab (Derrick Stolee 2025-02-03 17:11:07 +0000 1) /* We need this macro to access core_apply_sparse_checkout */
85127bcdeab (Derrick Stolee 2025-02-03 17:11:07 +0000 2) #define USE_THE_REPOSITORY_VARIABLE
85127bcdeab (Derrick Stolee 2025-02-03 17:11:07 +0000 3)
[...]
Running git backfill right after cloning is mostly useful when combined with sparse checkout. With the --sparse option (enabled by default when your repository has sparse checkout active), Git only downloads blobs for paths inside your sparse checkout — so you won't fetch objects you'd never check out anyway.
Faster Compression with zlib-ng
Git has long relied on zlib for its object and packfile compression. Popular forks like intel/zlib and cloudflare/zlib have added optimizations over the years, and the zlib-ng project merges many of those improvements while removing dead code and legacy compiler workarounds. zlib-ng includes SIMD support (SSE2, AVX2) in its core algorithms and is designed as a drop-in replacement for zlib.
Git 2.49 updates its compatibility layer so you can build against zlib-ng. Pass ZLIB_NG when using the GNU Make build, or set the zlib_backend option with Meson. Early experiments show roughly a 25% speed-up when dumping all objects in the Git repository — about 52.1 seconds down to 40.3 seconds.
Rust Enters the Git Codebase
A major milestone this release: the first Rust code is now checked in. Git 2.49 introduces two crates: libgit-sys, a low-level wrapper, and libgit, a higher-level wrapper around a small portion of Git's library code. The proof-of-concept wraps part of the config.h API and builds on the project's ongoing "libification" effort — replacing functions that exit the program with ones that return error codes, cleaning up memory leaks, and generally making Git's internals more library-friendly.
This isn't a complete wrapper around Git's whole interface, and there's plenty more work before that becomes realistic. But it's an important first step.
Cleanup and Garbage Collection Improvements
The libification push continues elsewhere. Many more commands in this release now use a provided repository parameter instead of relying on the global the_repository. The project also invested in squelching -Wsign-compare warnings, which flag comparisons between signed and unsigned values — a subtle class of bug where, for instance, -1 < 2 can evaluate to false due to two's-complement wrapping. These changes shouldn't be visible day-to-day, but they are groundwork for using Git as a library.
For users of git gc, there's a small but useful addition. In Git 2.39, git repack gained the --expire-to option, which lets you move pruned unreachable objects to a separate location instead of deleting them outright. git gc has now caught up with its own --expire-to option, so you can preserve those objects without dropping down to the lower-level repack command.
Autocorrect, Clone Options, and Deprecation Prep
Git's help.autocorrect feature now follows the usual boolean conventions. Previously, values like true, yes, on, and 1 meant different things — with a numeric value saying "wait this many deciseconds and run the command automatically." That meant setting help.autocorrect to 1 would execute a corrected command almost instantly rather than enabling the feature safely. In Git 2.49, 1 behaves like other boolean values, and larger positive numbers still introduce the delay as before. (You can no longer configure a delay of exactly one decisecond, but that was never meant to be a practical setting.)
$ git psuh
git: 'psuh' is not a git command. See 'git --help'.
The most similar command is
push
Cloning gets a new --revision option alongside the existing --branch. Whereas --branch expects a branch or tag name, --revision can point at any revision — useful in CI setups where you want the full history leading up to a commit that isn't at the tip of any branch.
Finally, the project is preparing for future breaking changes. Remotes were originally configured via files in $GIT_DIR/branches, then briefly via $GIT_DIR/remote, before the current config-based approach took over. Git has kept backwards compatibility for these old mechanisms for years, but they will be removed in Git 3.0. You can review the full list of planned removals in Documentation/BreakingChanges.adoc, or try compiling with the WITH_BREAKING_CHANGES switch to build a Git with those features already disabled.
Two Outreachy interns wrapped up projects in this cycle: Usman Akinyemi added support for including uname information in Git's HTTP user agent, and Seyi Kuforiji converted more unit tests to the Clar testing framework. Both projects were merged in time for this release.
Beyond the headline features
The improvements covered above are only the most visible part of what shipped in Git 2.49. A deeper look at the release notes for 2.49, or those for any earlier release in the Git repository, surfaces plenty more fixes, refactors, and performance tweaks across the codebase, from transport internals to credential handling to low-level object storage.
Typo tolerance tuned
One small but useful behavioral adjustment concerns help.autocorrect. The setting has always aimed to save you from fat-fingering a subcommand by waiting briefly before running the closest match. Previously, integer values represented tenths of a second, which made it hard to request a delay in the tens-of-milliseconds range without falling back to "immediately restore the old behavior" territory.
That math has been simplified: a value of 1 now means 100 milliseconds. That is not an arbitrary number — it is roughly the time it takes a human to blink1, and it is just long enough that the output displaying the correction will not be obscured by the command taking over the terminal. Anything below that threshold, and you are effectively approving the suggestion before you can even read it.
Higher values remain available in integer steps, each step being one additional 100-millisecond tick. The footgun of negative values triggering an immediate run without confirmation still exists, so keep the value small and intentional unless you really trust your shell history.
The full changelog is the reference for everything else, but those release notes are long and dense. If you are upgrading from an older version, these are the changes most likely to alter your daily workflows, so they are worth reading before git pull does something you do not expect on Monday morning.
- Human blink duration is commonly cited around 100–150 milliseconds; setting
help.autocorrectto 1 waits exactly 1 decisecond before executing the suggested command. ↩



