The Git project recently released Git 2.56.0. Let's look at some of the highlights of the release, including contributions from the Git team at GitLab.
What's covered:
- Git Merge 2026 and schedule for Git 3.0
- git-history(1) learns
drop - git-branch(1) learns to delete merged branches
- git-refs(1) learns to modify refs
- Google Summer of Code 2026
- Linearizing history with git-replay(1)
- Making the object database pluggable
This release cycle coincided with Git Merge 2026 held in Lisbon. The conference stretched over three days, consisting of a JJ Con day, a Git Talks day, and an unconference and Git Contributor Summit day. The Git talks will be available soon.
One of the main discussion points for the Git Contributor Summit was planning around the release of Git 3.0. The Documentation/BreakingChanges.adoc document in Git discusses the changes expected in 3.0, with the caveat that "There is no planned release date for this breaking version yet". That changed with the discussions held at the contributor summit.
The biggest changes scheduled for Git 3.0 include:
- SHA-256 as the default object hash format
- reftable as the default reference storage format
- Rust no longer being an optional dependency
While the code for these changes is available in current Git versions, they have varied levels of support in the larger Git ecosystem, which includes Git libraries, applications and forges. If the ecosystem doesn't support the changes, users would be left in limbo, where some tools support the new features while others fail.
For SHA-256 specifically, one of the biggest drawbacks was support on the forges. GitLab has been supporting SHA-256 since a year ago, while GitHub has announced a private beta at Git Merge with general availability following in a few months.
With that in mind, the discussions at the summit led to the following schedule for Git 3.0:
ReleaseTiming2.56September 2026 (this release)2.98December 20262.99 and 3.0Spring 2027The plan is to jump straight from 2.56 to 2.98; this significant jump serves as an indication to both downstream maintainers and consumers that a big change is coming. After that, both 2.99 and 3.0 will be shipped together, the only difference being that Git 3.0 will have the breaking changes feature flag flipped. This keeps feature parity between the 2.99 and 3.0 releases, and keeps the changes in 3.0 small and limited.
The 2.99 release will be an LTS release so that platforms without a Rust toolchain continue to get updates. As usual, the discussion on details will follow on the mailing list.
git-history(1) learnsdropIn our blog post for Git 2.55, we learned about the fixup subcommand for git-history(1). In this release, the command learns a new drop subcommand.
If you've ever wanted to delete an existing commit, the flow has been to use git rebase -i and then interactively mark individual commits to drop via an editor. The new drop subcommand allows users to do this in one single line.
$ git history drop <commit>
The provided commit is then removed, and its descendants are rebased on top of its parent. Like other subcommands within git-history(1), any changes made update all branches that are descendants of the original commit to point to the rewritten commit.
This patch was submitted by Patrick Steinhardt.
git-branch(1) learns to delete merged branchesTo clean up merged branches, I've generally used some variation of the command:
$ git branch --merged main | grep -v main | xargs git branch --delete
Unfortunately, this only deletes branches merged into the branch provided; it doesn't catch branches forked from main and merged into origin/main. This release adds two new flags to help with this.
First, the --forked flag, which is a listing filter used to show branches whose configured upstream matches the provided ref, remote name or branch.
$ git switch --create feature-a origin/main
Switched to a new branch 'feature-a'
branch 'feature-a' set up to track 'origin/main'.
$ git branch --forked origin
feature-a
feature-b
* main
This shows the branches which started from a particular integration branch. We can also see the status of each of the branches:
$ git branch -vv
feature-a b3ebf58 [origin/main: behind 1] Add feature A
feature-b 34c0403 [origin/main: ahead 1, behind 1] Add feature B
* main 230a3b0 [origin/main] Document the parser
Here feature-a is behind origin/main, meaning that the branch was merged into origin/main, while feature-b still needs to be. To plug this usability gap, the new option, --delete-merged, comes into play. It deletes all branches whose tips are reachable from their configured upstreams, effectively removing every feature branch that was merged into the integration branch.
$ git branch --delete-merged origin
Deleted branch feature-a (was b3ebf58).
Tip: These options rely on your branches tracking an integration branch. If you usually branch off your local integration branch instead, take a look at the
inheritsetting for thebranch.autoSetupMergeconfiguration.
This feature was submitted by Harald Nordgren.
git-refs(1) learns to modify refsgit-refs(1), introduced in Git 2.46, has been the new home for reference-related operations. Since its introduction it has grown several subcommands already to migrate references between storage backends, perform consistency checks, list existing references or check for existence of a reference. In this release, the command also learns to create, delete, update and rename references.
$ git refs list
689f6e8164e070ed133d5176806ad69e31d22142 commit refs/heads/branch-d
689f6e8164e070ed133d5176806ad69e31d22142 commit refs/heads/branch-r
689f6e8164e070ed133d5176806ad69e31d22142 commit refs/heads/branch-u
689f6e8164e070ed133d5176806ad69e31d22142 commit refs/heads/main
$ git refs create refs/heads/branch-c main
$ git refs update refs/heads/branch-u main~1
$ git refs rename refs/heads/branch-r refs/heads/foo
$ git refs delete refs/heads/branch-d
$ git refs list
689f6e8164e070ed133d5176806ad69e31d22142 commit refs/heads/branch-c
c4e9b6bafb7ac9a96c5d9337cd60a0ec6d5a240d commit refs/heads/branch-u
689f6e8164e070ed133d5176806ad69e31d22142 commit refs/heads/foo
689f6e8164e070ed133d5176806ad69e31d22142 commit refs/heads/main
The update and delete subcommands also take an optional expected old value, which validates the previous value before performing the operation.
These patches were implemented by Patrick Steinhardt.
Google Summer of Code 2026The release also marks the successful completion of all four Google Summer of Code projects, each of which was co-mentored by a GitLab engineer.
Add remote-object-info command to git-cat-file(1)
Working in a partial clone allows repositories to operate without downloading every object. Git lazily fetches objects on demand. While git-cat-file(1) supports reading object-info for a given object hash, it only works by transferring the entire object. Git 2.56 adds a new remote-object-info command to git-cat-file(1)'s --batch-command mode, which requests only the metadata over v2 of the Git protocol. Consider a blobless clone that contains no blobs at all:
$ git rev-parse HEAD:model.weights
42ec5fc0f485f764cdf0eaa85ab061c055f5f518
$ git cat-file --batch-all-objects --batch-check | grep -c blob
0
$ echo "remote-object-info origin 42ec5fc0f485f764cdf0eaa85ab061c055f5f518" |
git cat-file --batch-command
42ec5fc0f485f764cdf0eaa85ab061c055f5f518 blob 3000000
$ git cat-file --batch-all-objects --batch-check | grep -c blob
0
This allows us to obtain the size of the blob without transferring the blob itself: three million bytes of network bandwidth saved.
The project was worked on by Pablo Sabater, continuing previous work started by Eric Ju and Calvin Wan, and mentored by Karthik Nayak and Chandra Pratap.
Improve disk space recovery for partial clones
Partial clones save considerable disk space by only lazily fetching blobs. With time, those fetches accumulate and the repository grows, losing the advantage. This GSoC project involved adding a new --drop-filtered option to git-repack(1) which finds large promisor blobs above the given filter and removes them locally.
Here is a filtered clone of a repository whose history contains a 3MB blob. Fresh out of git clone, the repository size is small, but just looking at the blob fetches it onto the local disk:
$ du -sh .git
144K .git
$ git cat-file -s b8086a678595c9fce29d980f7a12ca069b466f6c
3000000
$ du -sh .git
3.1M .git
While this blob isn't required in the working tree, nothing removes it. Running --drop-filtered drops the blob, allowing Git to lazily refetch it when needed in the future.
$ git repack -a -d --filter=blob:limit=1k --drop-filtered
$ du -sh .git
156K .git
This project was worked on by Siddharth Shrimali and mentored by Christian Couder and Siddharth Asthana.
Improve the new git repo command
The git-repo(1) command retrieves information about the repository. As part of the GSoC project, the git repo info subcommand was extended with path.gitdir and path.commondir, each available in both absolute and relative forms.
$ git repo info --all
layout.bare=false
layout.shallow=false
object.format=sha1
path.commondir.absolute=/home/karthik/code/git/.git
path.commondir.relative=.git
path.gitdir.absolute=/home/karthik/code/git/.git
path.gitdir.relative=.git
references.format=reftable
Further patches to add more path based information are currently on the mailing list.
This project was worked on by K Jayatheerth and mentored by Justin Tobler and Lucas Seiki Oshiro.
Reduce Git's global state
Git's core codebase still heavily relies on global state, from the famous the_repository variable to numerous config options. This acts as a blocker to the libification efforts in Git. This project moves some of the global config variables into a new repo_config_values struct on struct repository. Call sites get updated to pass the struct repository explicitly instead of using the global the_repository.
This project was worked on by Tian Yuchen, mentored by Christian Couder, Ayush Chandekar and Bello Caleb Olamide.
Linearizing history with git-replay(1)git-replay(1) is an experimental Git command which allows server-side rebases and doesn't require a working tree to operate:
$ git log --oneline --graph --all
* 1756840 (main) main: unrelated upstream work
| * 286e7f1 (topic) topic: add two
| * 4324bcd topic: add one
|/
* 9497867 Initial commit
$ git replay --ref-action=print --onto main topic
update refs/heads/topic a144de2fc13ac40c8a481052be5e4d42071c4908 286e7f186f229ac4dd5ecb68a372951003f16224
$ git log --oneline --graph a144de2fc13ac40c8a481052be5e4d42071c4908
* a144de2 topic: add two
* 6422f37 topic: add one
* 1756840 (main) main: unrelated upstream work
* 9497867 Initial commit
Until now there was one issue: the command didn't work with commit ranges containing merge commits. Consider a scenario where you're working on a feature branch, and a bugfix which lands on main is also merged into your feature branch. This causes the feature branch to contain a merge commit as shown:
$ git log --graph --oneline --decorate --all
* cb996b6 (HEAD -> feature) feature: handle the empty-input edge case
* 4f76f3d Merge branch 'bugfix' into feature
|\
| * 4959734 (main, bugfix) bugfix: do not crash on null input
* | 829b62c feature: add parser tests
* | 7fd60db feature: add the parser
|/
* 9497867 Initial commit
$ git replay --onto main main..feature
fatal: replaying merge commits is not supported yet!
As you can see, git-replay(1) refuses to rebase the branch due to the existing merge commit. In Git 2.56 we now have --linearize, which is similar to the already default --no-rebase-merges option in git-rebase(1).
$ git replay --linearize --onto main main..feature
$ git log --graph --oneline --decorate --all
* da8df6e (HEAD -> feature) feature: handle the empty-input edge case
* f68889f feature: add parser tests
* f13885b feature: add the parser
* 4959734 (main, bugfix) bugfix: do not crash on null input
* 9497867 Initial commit
This feature was submitted by Toon Claes.
Making the object database pluggableIn the 2.54 release blog we discussed what pluggable object databases are and how they work. As a primer, Git stores its objects (blobs, trees and commits) under .git/objects in two different formats: "loose" objects, which are individual files, and "packfiles", which consist of objects compacted together. Since there was no abstraction layer, different subsystems reached directly into the storage layer to interact with objects. This changed in 2.54 as an abstraction layer was finally put in place.
This release takes further steps in the direction of providing the required interfaces in the backends to make them truly pluggable, namely:
- Creating the on-disk data structures. During the initialization of the repository, the
.git/objectsfolder gets created. This responsibility is now moved to individual backends. - Consistency checks. The consistency checks that are part of git-fsck(1) have now moved into the object source layer, so backends can implement their own consistency checks.
- Housekeeping. The existing files-backend-specific housekeeping logic that exists as part of git-gc(1) and git-maintenance(1) (incremental repacking, geometric repacking and pruning) has moved out of the command and into the files object source.
- Packfile generation. Git produces packfiles as part of the protocol during the fetch/push commands by making a call to git-pack-objects(1). Now, backends can serve packfiles themselves.
- Individual object sources. Following the loose object backend in the last release, the packed object backend has now also been moved into its own object source.
- Read and write streams. The separate read and write object streams are now unified under a single
struct odb_stream, making it possible to stream arbitrary object types. - Using ODB transactions. git-receive-pack(1) is now adapted to use ODB transactions, which ensures that the command writes objects via the interface without making assumptions about the backend.
And much more. While a multitude of changes landed in this release, from the user's perspective everything stays the same, which is as intended. This provides us at GitLab with the infrastructure required to build Git at scale, by being able to swap out the backend as needed, while doing so as part of the Git community. Stay tuned, as more details are to follow.
These changes were collaboratively submitted by Justin Tobler and Patrick Steinhardt.
Read moreThis article highlighted just a few of the contributions made by GitLab and the wider Git community for this latest release. You can learn about these from the official release announcement of the Git project. Also, check out our previous Git release blog posts to see other past highlights of contributions from GitLab team members.