$RodHat_
MOTD

Git's SHA-256 transition is finally not vaporware

Published by

Git's SHA-256 transition is finally not vaporware
Photo: AI-generated — no human photographer / RodHat AI Cover

If you’ve been watching the Git project’s SHA-256 work since --object-format=sha256 landed in 2.29 (October 2020), you’re familiar with the feeling: it exists, it technically works, it doesn’t interoperate with anything that matters. GitHub, GitLab, and every CI/CD integration that touches raw object IDs have been quietly ignoring it for six years because the ecosystem hadn’t moved.

That’s finally changing. The major hosting providers are piloting SHA-256 repository support with real interop semantics, and the wire protocol work that makes it not-terrible is shipping in recent Git releases. This is the moment where SHA-256 stops being a flag you set on a personal research repo and starts being something you could actually migrate a production codebase to.

I have feelings about both the delay and the design.

Why SHA-1 in Git is a specific kind of problem

The SHAttered attack (2017) demonstrated a chosen-prefix SHA-1 collision on real PDF documents. The practical attack on Git objects is harder — you’d need to produce two different blob or tree objects with the same hash, which is more constrained than free-form collision — but the cryptographic community’s position since 2017 has been “SHA-1 is done, plan accordingly.” Git’s position for most of that time was “our specific use of SHA-1 is fine, actually” plus “migration is incredibly hard.”

Both things were true. SHA-1’s use in Git is not the same as SHA-1 in TLS certificates, and the actual attack surface for hash collisions in a Git repo is narrower than the raw number suggests. But “narrower than you think” is not the same as “fine,” and the migration being hard isn’t an argument against doing it — it’s an argument for starting earlier, which we didn’t.

The longer-term consequence of sitting on SHA-1 is that every tool in the ecosystem that passes object IDs around — CI systems, code review platforms, deployment pipelines, hooks that grep for 40-hex-character strings — is silently encoding an assumption about hash length and algorithm into interfaces that are annoying to change later. The technical debt isn’t in the object store. It’s in every script that pattern-matches on [0-9a-f]{40}.

What the interop design actually looks like

This is the part that earned my grudging respect. The naive approach to SHA-256 migration is “announce a flag day, break everything.” The Git project didn’t do that.

The wire protocol now includes capability advertisement: a SHA-256 client can negotiate with a SHA-1 server using a translation proxy layer. Git’s own protocol v2 extensions expose the server’s object format in the capability negotiation phase, so a modern client knows before the first ls-refs call whether it’s talking to a SHA-1 or SHA-256 repo. For the mixed case — SHA-256 local, SHA-1 remote — the proxy mode maintains a bidirectional mapping table and rewrites object IDs across the transport layer. You clone a SHA-1 remote into a SHA-256 local repo and Git handles the impedance mismatch.

It’s more moving parts than I’d prefer, but it’s the correct tradeoff given the installed base. Every SHA-1 repo that has ever existed still works. No flag day. The transition is opt-in until the day the hosting providers flip the default, which is still years out.

Compare this to how OpenSSH handled the legacy algorithm removal: the SSH team eventually drew a hard line on which algorithms get compiled out, because the set of people relying on arcfour in 2025 was small enough to absorb the pain. Git can’t do that — the installed base of SHA-1 repos is every Git repo that has ever existed. The interop approach is the only path that doesn’t require a coordinated global migration event.

What this actually changes for you, today

Not much yet, unless you’re on the early-adopter path deliberately. The SHA-256 format is still not the default in any major hosting provider’s UI. You can create SHA-256 repos if you know what you’re doing, and now you can push/pull them to a capable remote. But your GitHub Actions workflows are still getting SHA-1 object IDs from ${{ github.sha }}, your deployment scripts still check 40-character strings, and your pre-receive hooks still grep for the old format.

The practical value right now is: if you have a new, greenfield codebase that you’re starting today, starting it as SHA-256 is now defensible rather than experimental. The toolchain will have friction in places. But you’re not setting yourself up for a harder migration later.

For anything existing: wait. The tooling isn’t there yet. The migration tooling (git-migrate-objects, the plumbing under it) is functional but not polished. Give it another 18 months and the hosting-side support will have matured enough that the migration path is documented and tested against real-world repo shapes, not just toy repositories in the test suite.

The boring lesson

Cryptographic migrations are slow because they have to be. The SHA-1 to SHA-256 transition in Git has taken longer than it should have, but the design they shipped is sound: backward compatible, protocol-negotiated, no flag day. That’s harder to build than a flag day and easier to live with than the alternative.

The same discipline applies to anything you’re maintaining in a long-lived system: the ABI assumptions you encode today are the migration problem you inherit in eight years. The Git project took six years to ship a migration design that doesn’t break everyone. I would have preferred four, but I’d rather have the careful design than the fast one.

Object IDs are infrastructure. SHA-1 object IDs are infrastructure you can no longer trust. The path forward is real now. Use it appropriately.