Git has been able to create repositories that name objects with SHA-256 since version 2.29.[9] Core Git no longer labels that local storage format experimental.[2][6] Those facts are easy to demonstrate and dangerous to overinterpret. In August 2026, Git's git-init documentation still says that SHA-1 is the default and that SHA-256 repositories do not interoperate with SHA-1 repositories.[2] Its compatObjectFormat machinery is explicitly incomplete and intended for development, not end-user deployment.[3]
For an existing repository whose production path still depends on SHA-1-only peers or tooling, the useful migration move today is therefore a rehearsal. Keep that canonical repository in its present format. Create a disposable SHA-256 repository, synthesize its representative topology—merges, tags, notes, and other shapes your real history contains—then send that canary through every client and integration that touches production. Record where an object ID was treated as an opaque identifier versus where somebody quietly encoded “40 hexadecimal characters.” That audit creates value even if the production cutover remains years away.
Git's planned breaking-release changes make the work timely without making it urgent in the reckless sense. The project intends a future major release to use SHA-256 by default for newly initialized repositories, while retaining SHA-1 as a supported object format. The same plan makes ecosystem readiness—ahead of any date on a calendar—a prerequisite: libraries, applications, and forges must understand the new format.[1]
Image context: the cover photograph shows the Git Merge 2022 venue, where contributors and users met around Git itself rather than a single hosting product. That is the right scale for this transition: an object identifier passes through a community of tools long before it reaches a human reader.[8]
Start with a repository you can delete
A useful lab begins with ordinary Git commands and no production remote:
git init --object-format=sha256 sha256-lab
cd sha256-lab
printf '%s\n' 'object-format rehearsal' > README.md
git add README.md
git -c user.name='SHA-256 Lab' \
-c user.email='[email protected]' \
commit -m 'Create the rehearsal repository'
git rev-parse --show-object-format=storage
git rev-parse HEAD
git fsck --full
The first inspection should print sha256; the full commit name should contain 64 hexadecimal characters, rather than SHA-1's 40. git rev-parse --show-object-format=storage is the interface to ask which algorithm a repository uses. It is safer than guessing from the length of one value or reading .git/config with an ad hoc parser.[4]
This is not the same history with longer labels pasted on. Git computes an object ID over a header containing the object's type and length, a NUL byte, and its serialized content. Trees, commits, and tags also contain names of other objects, so their serialized SHA-256 form refers to a different object namespace. Blob content does not contain child object names, but the algorithm used to name that blob still changes its identifier. Git's transition design provides for round-trip conversion and a verified mapping between the two names; it does not pretend that one name can be mechanically padded into the other.[5]
The repository-format extension creates another deliberate boundary. Git records the non-default format under repository format version 1, allowing older clients that do not understand the extension to stop instead of opening the repository under false assumptions.[5] A clean failure is useful evidence. If a supported workstation, build image, IDE, library, backup agent, or server cannot open the lab repository, the migration has found a blocker before it acquired production consequences.
Do not turn this exercise into an improvised conversion procedure. Git's documentation says extensions.objectFormat should be set only by git init or git clone; editing it after initialization can leave a repository in a state that is difficult to diagnose.[3] GitLab documents a SHA-256 project-creation option behind the disabled-by-default support_sha256_repositories feature flag; it labels the feature an experiment for testing, not production, and says Git cannot migrate an existing project to SHA-256 or back to SHA-1.[6] Those are strong reasons to preserve a simple rollback: delete the canary, not rewrite the source of truth.
Forty characters escaped into the surrounding system
The migration's most revealing code is usually outside Git. Object IDs are copied into database columns, cache keys, artifact names, URLs, webhook schemas, deployment records, release manifests, provenance attestations, chat messages, and incident tickets. Some consumers validate the value; others truncate it; a few store the raw 20-byte SHA-1 digest rather than its 40-character hexadecimal form. SHA-256 changes those two familiar widths to 32 bytes and 64 hexadecimal characters.[5]
Begin with a source and schema search. These patterns are deliberately broad; each match needs human interpretation:
rg -n --hidden \
'CHAR\(40\)|VARCHAR\(40\)|BINARY\(20\)|VARBINARY\(20\)|\{40\}|\b40\b' \
.
rg -n --hidden \
'sha1|commit[_-]?sha|commit[_-]?hash|object[_-]?id|git[_-]?oid' \
.
Look beyond application code. Search SQL migrations, protobuf and JSON schemas, log parsers, CI templates, shell parameter slicing, regular expressions, dashboard fields, Terraform state, message-queue contracts, fixtures, and test factories. Then inspect dependencies that never appear as source: forge SDKs, Git libraries, IDE plugins, admission controllers, release bots, and security scanners.
Widening CHAR(40) to CHAR(64) is only a local repair. At a durable external interface—this is application schema, not Git command-line syntax—store the algorithm alongside the full hexadecimal object name, for example as sha1:<40-hex> or sha256:<64-hex>, or use a typed structure with algorithm and hex fields. The qualifier prevents a parser from treating length as meaning. It also gives a future translation service enough information to answer the right question.
Abbreviated names need a separate audit. Seven- or twelve-character commit prefixes are presentation conveniences whose uniqueness depends on the objects present in a particular repository. They are unsuitable as database keys, cross-system join fields, or permanent release evidence. Let Git abbreviate for a human display; carry the complete, algorithm-qualified name across machine boundaries.
Git's own transition plan offers a useful engineering clue. Among its preparatory tasks was replacing hard-coded raw and hexadecimal sizes—20 and 40—with object-ID abstractions and maximum-size constants.[5] Downstream systems should adopt the same posture: pass an object ID as an object ID, not as a string whose incidental width became a protocol.
Follow every identifier across its crossings
A green git status proves the core executable can read one repository. It says nothing about the chain that turns a commit into a reviewed, tested, released, deployed, and recoverable artifact. Build the rehearsal around crossings:
- Developer to forge. Create branches, push and fetch them, open and merge a change, create an annotated tag, and inspect every URL and API response that exposes an object name.
- Forge to automation. Trigger push, tag, merge-request, and release webhooks. Confirm that the complete name reaches queue payloads, CI environment variables, status APIs, deployment services, and notifications without rejection or truncation.
- Runner to toolchain. Exercise every supported Git binary and library, not only the newest CLI on a maintainer's laptop. Include language bindings, source generators, changelog tools, version embedders, scanners, and any program that walks
.git/objectsdirectly. - History to artifact. Build packages and containers, publish checksums and software bills of materials, attach provenance, then trace the recorded object name back through the same interfaces used during an audit.
- Service to recovery. Back up the repository and its forge metadata, restore both into an empty environment, run
git fsck --full, and repeat a clone and build. A bare-repository copy without issues, merge requests, protected-ref settings, CI variables, or release metadata is not a full recovery test.
Use exact payload capture at each seam. A passing UI can conceal a truncated database field; a successful build can conceal an attestation tied to the wrong identifier; a webhook consumer can return HTTP 200 after discarding an unfamiliar value. The acceptance evidence should include the value produced, the value stored, the value emitted, and the value later resolved.
The compatibility matrix must include less glamorous repository shapes. Seed the canary with a merge commit, annotated and signed tags, Git notes, a submodule, a large-file pointer if the organization uses one, and representative branch and tag counts. Exercise shallow and partial clones, bundles, mirrors, worktrees, garbage collection, archive generation, and backup restoration where they are part of the supported path. Git's transition design specifically identifies shallow history, submodules, alternates, notes, signatures, and server-side conversion cost as areas that need special handling; those warnings should become test cases rather than footnotes.[5]
Preserve old names as records, not debris
An existing SHA-1 object name may appear in a vulnerability advisory, a Fixes: trailer, a release announcement, an issue comment, a mailing-list patch, or a customer's support case. Those references are records of what someone meant at a particular time. A migration that makes them unresolvable has damaged the history even if every source file survives.
Git's intended interoperability model addresses this inside a repository with bidirectional mappings between SHA-1 and SHA-256 names. Its staged design also allows input and output formats to change at different points in the transition.[5] The current implementation boundary is crucial, however: compatObjectFormat remains incomplete and is not an end-user migration switch.[3] Do not design today's cutover around a translation facility that the current manual says is still for development.
Instead, inventory external references now. Record which systems hold full IDs, which hold abbreviations, and which can attach an algorithm qualifier without rewriting the old value. When production-grade translation arrives, preserve mappings as durable data with their repository identity and verification status. A bug report that said sha1:abc… should continue to say that; a resolver may add the corresponding SHA-256 name, but it should not silently edit the historical citation.
Signatures require the same restraint. Git's transition design anticipates SHA-1 and SHA-256 signature fields for commits and tags, including a period in which objects may carry both. It also notes that an older verifier can treat a SHA-256-only signed commit as unsigned.[5] That makes git verify-commit and git verify-tag only the first checks. The rehearsal must also inspect the forge's “verified” state, CI signature policy, release-attestation verifier, key-discovery path, and the evidence retained by audit systems. Do not re-sign old releases merely to make a dashboard green; establish whether policy calls for preserving, mapping, or newly attesting each historical object.
Use four stages with explicit stop conditions
Stage 1: static inventory. Find fixed widths, untyped object-name fields, abbreviations used as keys, and direct .git readers. Record each owning team and each supported version. Exit only when every authoritative data store and integration has an owner, even if many fixes remain open.
Stage 2: local SHA-256 laboratory. Run the organization's ordinary developer, test, packaging, signing, and recovery commands against a disposable repository created with --object-format=sha256. Exit only when failures are reproducible and classified. “Works on the platform team's laptop” is not an exit criterion.
Stage 3: forge canary. Use a non-production project only where the forge explicitly enables SHA-256 testing. Synthesize realistic history and execute the full crossings above with no dependency on the production repository. On a GitLab instance where its experimental flag is enabled, the SHA-256 creation path is one possible lab, not evidence that every other forge or integration is ready.[6] Exit only when the canary can be destroyed without losing authoritative work and every supported client has a recorded result.
Stage 4: wait for the migration contract. A production proposal needs a supported creation or conversion route, an interoperability story, durable old-to-new name resolution, verified backup and restore, and a documented response when a client emits the other algorithm. Independent reporting on Git's work has repeatedly identified interoperability—not local SHA-256 object storage—as the difficult ecosystem problem.[7] Until that contract is shipped and tested, the correct state is “prepared, not converted.”
Team size changes the depth of the matrix, not the boundary. A two-person project that uses one current Git binary and a filesystem remote can finish a useful rehearsal quickly. A platform serving hundreds of repositories through multiple runners, SDKs, mirrors, signing systems, and compliance exports needs owners and evidence at every crossing. Where any required production path still fails, keep the canonical repository outside the experiment. A greenfield repository with fully controlled clients and remotes can be a legitimate adoption canary, but it does not prove that a shared toolchain is ready.
The falsifier is straightforward: production migration is not ready if a required producer or authoritative consumer cannot preserve a full object ID together with unambiguous algorithm context, if a consumer truncates or rejects it, or if the supported forge lacks the required interoperability and recovery path. A future default may start the conversation. It cannot substitute for a passing system.
That is why the best SHA-256 work in 2026 does not begin by converting history. It begins by removing a hidden API from the systems around Git: the belief that an object ID is always 40 characters long.
Sources
- Git project, “BreakingChanges,” Git 2.55.0 snapshot — planned future-major defaults, SHA-1 risk context, continued SHA-1 support, and the requirement that libraries, applications, and forges be ready for SHA-256.
- Git project,
git-initdocumentation, Git 2.55.0 snapshot —--object-format, the current SHA-1 default, and the present lack of SHA-1/SHA-256 repository interoperability. - Git project,
git-configdocumentation, Git 2.55.0 snapshot —extensions.objectFormat, the warning against changing it after initialization, and the development-only status of incompletecompatObjectFormatsupport. - Git project,
git-rev-parsedocumentation, Git 2.55.0 snapshot — repository object-format discovery and object-name input/output controls. - Git project, “Hash Function Transition” technical design, Git 2.55.0 snapshot — object serialization, dual-name mapping, signatures, transition modes, caveats, and implementation stages.
- GitLab documentation, “Create a project that uses SHA-256 hashing” — experimental test availability, creation-time selection, and the current absence of migration in either direction.
- Jonathan Corbet, “Git considers SHA-256, Rust, LLMs, and more,” LWN.net, October 21, 2025 — independent reporting on interoperability work and ecosystem readiness.
- Lee Reilly, “Git Merge 2022 — that's a wrap!”, GitHub Blog, October 21, 2022 — event report and source page for the article's archival conference photograph.
- Taylor Blau, “Highlights from Git 2.29,” GitHub Blog, October 19, 2020 — contemporary account of Git's first experimental SHA-256 repository support and its interoperability boundary.