This post is the short version of my video. Watch it here: Git 3.0 Is Coming: I Built It and Tried Every Breaking Change
Git 3.0 does not exist yet. The project’s own list of breaking changes says there is “no planned release date”. What does exist is a compile-time flag, WITH_BREAKING_CHANGES, that makes today’s Git behave the way 3.0 is meant to.
These are my notes from building Git with that flag and poking at it next to the Git I use every day (2.43). The video walks through the same experiment; this page is the reference version, with the commands you need to repeat it.
The short version
| Change | What I saw in the preview build | Touches existing repositories? |
|---|---|---|
Default branch main | Yes, with a hint that mentions Git 3.0 | No |
| Refs stored as reftable | Yes, new repositories get it | No |
| SHA-256 as the default hash | Reported by the build, not applied by git init | No |
git whatchanged, git pack-redundant removed | Gone | The commands disappear everywhere |
| Bare repositories only when named explicitly | Yes | Yes, it is a behaviour change |
| Rust required to build Git | Build reports rust: enabled | Only matters if you build Git |
Everything in the first three rows is a default for new repositories. Nothing converts the repositories you already have.
Build it yourself
I built the v2.56.0 tag into its own folder, so the Git that came with my machine stayed untouched:
git clone --depth 1 --branch v2.56.0 https://github.com/git/git
cd git
make -j12 WITH_BREAKING_CHANGES=YesPlease NO_GETTEXT=1 NO_TCLTK=1 \
NO_PERL=1 NO_PYTHON=1 NO_EXPAT=1 prefix="$HOME/git3" install
You need a Rust toolchain: since 2.55 the build expects one unless you switch it off. The NO_* options only skip optional pieces I did not need.
The flag is a single line in the Makefile, and it flips defaults like this one in hash.h:
sed -n 196,200p git-src/hash.h
#ifdef WITH_BREAKING_CHANGES
# define GIT_HASH_DEFAULT GIT_HASH_SHA256
#else
# define GIT_HASH_DEFAULT GIT_HASH_SHA1
#endif
I linked the result as git3, so in everything below git is 2.43 and git3 is the preview:
git3 version --build-options | grep -E "^git|rust|default"
git version 2.56.0
rust: enabled
default-ref-format: reftable
default-hash: sha256output
Change 1: main
Nothing surprising here, but the wording of the hint is new:
git3 init new
hint: Using 'main' as the name for the initial branch since Git 3.0.
hint: If you expected Git to create 'master', the just-createdoutput
Change 2: SHA-256, with a catch
The build claims SHA-256 as its default hash. A freshly initialised repository disagrees:
git3 -C new rev-parse --show-ref-format --show-object-format
reftable
sha1output
Reftable is on, the hash is still SHA-1. I checked the same code on Git’s master branch and it reads the same way, so as of 2.56 the headline change is the one that is not wired into git init yet. You can ask for it explicitly:
git3 init --object-format=sha256 new256
From there the difference is easy to see. The same one-line file gets two unrelated names:
git -C old hash-object hello.txt
ce013625030ba8dba906f756967f9e9ca394464aoutput
git3 -C new256 hash-object hello.txt
2cf8d83d9ee29543b34a87727421fdecb7e3f3a183d337639025de576db9ebb4output
40 hex characters become 64, for blobs, trees and commits alike:
git3 -C new256 cat-file -p HEAD
tree c7187e8fdb691b3a692e5f3f0bbcb6359e5046285225f18f9773d4fe54268c55
author Huseyin <huseyin@example.com> 1791208116 +0200
committer Huseyin <huseyin@example.com> 1791208116 +0200
first commitoutput
git3 -C new256 ls-tree HEAD
100644 blob 2cf8d83d9ee29543b34a87727421fdecb7e3f3a183d337639025de576db9ebb4 hello.txtoutput
That last line is the reason the change is so invasive. A tree lists blobs by hash, a commit names a tree by hash, and every commit names its parent by hash. Swap the function and every identifier in the repository is different.
The part that will hurt
A SHA-1 repository and a SHA-256 repository cannot exchange objects. In either direction:
git3 -C new256 fetch ../old
fatal: mismatched algorithms: client sha256; server sha1output
git3 -C old fetch ../new256
fatal: mismatched algorithms: client sha1; server sha256output
So this is not a setting you flip per developer. A project is on one side or the other, together with its hosting, its CI and every tool that reads the repository. Git’s own document says the default will only change once that ecosystem is ready.
Whether it is worth it is being argued right now. The Git project points to the published collision research against SHA-1 and wants to move before it gets worse. Scott Chacon’s post on the GitButler blog makes the case that the migration cost is huge and the practical gain close to zero. Both are linked at the bottom; read them before you form an opinion.
Change 3: reftable
This one got far less attention and is the one that actually stopped my tools.
In a classic repository a branch is a small file:
ls old/.git/refs/heads && cat old/.git/refs/heads/master
master
e64246e0a455b25b01785df3f2c96dc7c6545e73output
With reftable there is no such file. The heads folder stays empty and the references live in a binary table:
ls new256/.git/refs/heads new256/.git/reftable
new256/.git/refs/heads
new256/.git/reftable:
0x000000000001-0x000000000003-5da030ff.ref
tables.listoutput
HEAD is still a file, but it no longer says where you are:
cat new256/.git/HEAD
ref: refs/heads/.invalidoutput
Git itself has no trouble listing the branches:
git3 -C new256 for-each-ref
581c4d6dc68dd60ed9e4ac9b35332ea9850fde32175b88d8d4fdfe9f7d9547f1 commit refs/heads/feature
581c4d6dc68dd60ed9e4ac9b35332ea9850fde32175b88d8d4fdfe9f7d9547f1 commit refs/heads/mainoutput
The motivation is practical: with one file per branch, two branches that differ only in upper and lower case collide on macOS and Windows, and repositories with a very large number of refs get slow. One sorted table avoids both.
Now the catch. My regular Git cannot open this repository at all:
git --version && git -C new256 status
git version 2.43.0
fatal: unknown repository extension found:
refstorageoutput
Reftable support arrived in Git 2.45, and 2.43 does not know the refstorage extension. Anything bundling an older Git, an IDE, a CI image, a container base image, will fail the same way the day it meets a reftable repository.
The smaller ones
git whatchanged is no longer a command:
git3 whatchanged
git: 'whatchanged' is not a git command. See 'git --help'.output
git log --raw gives you the same information:
git3 -C new256 log --raw --oneline
581c4d6 first commit
:000000 100644 0000000 2cf8d83 A hello.txtoutput
And Git stops treating a bare repository as usable just because you are standing in it:
git3 init -q --bare server.git && cd server.git && git3 log
fatal: cannot use bare repository '~/git3/server.git' (safe.bareRepository is 'explicit')output
Naming it works:
cd server.git && git3 --git-dir=. rev-parse --is-bare-repository
trueoutput
Rust
Git 3.0 makes Rust a hard build requirement. If you install Git from a package manager you will not notice. If you package Git, or build it for an unusual platform, this is probably the biggest item on the list, which is why the project plans to make the last 2.x release a long-term support version.
What I would do now
- Nothing urgent. There is no date, and existing repositories are not rewritten.
- Check your oldest Git. Find the oldest version in your CI images and developer tooling. If it is older than 2.45, reftable repositories will not open there.
- Try it on a throwaway repository. On a recent Git,
git init --object-format=sha256 --ref-format=reftablegives you both new formats today, and shows you quickly which of your tools cope. - Do not assume “Git 3.0 means SHA-256 everywhere”. In the code I built, that default is not active for new repositories yet.