Skip to content
Huseyin Babal
Go back

Trying Git 3.0 before it exists: SHA-256, reftable and what broke

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

ChangeWhat I saw in the preview buildTouches existing repositories?
Default branch mainYes, with a hint that mentions Git 3.0No
Refs stored as reftableYes, new repositories get itNo
SHA-256 as the default hashReported by the build, not applied by git initNo
git whatchanged, git pack-redundant removedGoneThe commands disappear everywhere
Bare repositories only when named explicitlyYesYes, it is a behaviour change
Rust required to build GitBuild reports rust: enabledOnly 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

Sources


Share this post:

Previous Post
How AI agents actually work: what happens on a tool call
Next Post
1000 trick questions for four decision models: what the numbers hide