
In brief
You cannot fetch a SHA-1 repository into a SHA-256 one — Git refuses with a mismatched-algorithms error. Convert through fast-export and fast-import instead, and treat every hardcoded 40-character hash as a bug.
Git hashes every object — blob, tree, commit — and stores it under that hash. Since 2005 that hash has been SHA-1, and the collision attacks published against SHA-1 are the reason the project added a second object format. Git 3.0 is planned to make SHA-256 the default, which turns a theoretical question into a scheduling problem for anyone maintaining a large repository.
This guide does the conversion on a real repository, shows the exact error you hit if you try the obvious clone approach, and lists the hardcoded assumptions that break when a 40-character hash becomes 64 characters.
01What changes when the object format changes
Git is a content-addressed database: an object's name is the hash of its content, so the hash function is part of the repository's identity. Two consequences follow immediately.
- Every object gets a new name, so every commit hash changes.
- A SHA-256 repository and a SHA-1 repository cannot share objects directly, because the same content hashes differently in each.
That second point is the one people underestimate. You cannot graft a SHA-256 clone onto a SHA-1 history and push; the object graphs are not compatible.
Fixed in your plan: this is a rewrite, not a setting
Switching object format changes the identity of every commit. Anything pinned to a SHA-1 commit — CI actions, release tags, submodule commits, deployment records — points at an object that no longer exists in the converted repository.
02Check what your repositories actually use
Start by measuring, not guessing. Every command below is runnable against a local repository.
git --version
# git version 2.50.1 (Apple Git-155)
git rev-parse --show-object-format
# sha1
rev-parse --show-object-format prints the format of the repository you are standing in. Run it across your checkouts to find which ones are still SHA-1, then size the job by object count.
git count-objects -vH
git rev-list --all --count
For a quick correctness probe, hash the same bytes in a SHA-1 repository and a SHA-256 one. The digest length and the digest itself both change:
cd sha1-repo && printf 'hello\n' | git hash-object --stdin
# ce013625030ba8dba906f756967f9e9ca394464a
cd sha256-repo && printf 'hello\n' | git hash-object --stdin
# 2cf8d83d9ee29543b34a87727421fdecb7e3f3a183d337639025de576db9ebb4
Create a scratch SHA-256 repository to see the difference without touching your real work:
git init --object-format=sha256 scratch
cd scratch && git commit --allow-empty -m init
git rev-parse HEAD
# a7bd209de3540f82c9736ffd0422615b8d8ebb7009ee32a6d9303f93f2807342
The commit id is 64 hex characters, not 40. That single difference ripples through every script that validates or truncates a hash.
03The obvious approach fails — here is the error
Creating a SHA-256 repository and adding a SHA-1 repository as a remote does not work. Fetching gives a hard error:
$ git init --object-format=sha256 converted
$ git -C converted remote add origin ../legacy.git
$ git -C converted fetch origin
fatal: mismatched algorithms: client sha256; server sha1
This is not a permissions problem or a missing flag; the transports refuse to move objects between formats. It rules out the migration strategy most people reach for first.
Do not plan around a cross-format clone
git clonehas no object-format flag, and fetch across formats fails on current Git. If a guide tells you to clone into SHA-256, it is describing a version or a patch that does not ship yet.
04Convert through fast-export and fast-import
The reliable path is to serialize history as text and replay it into a new repository, letting the new repository hash everything with SHA-256. This is exactly what fast-export and fast-import are for, and it is verified below on a two-commit repository.
- Create the destination repository with the SHA-256 format.
git init --object-format=sha256 converted
- Stream every ref from the old repository into the new one. Run this from the source checkout, pointing at the destination path.
git fast-export --all --signed-tags=verbatim | \
(cd ../converted && git fast-import --quiet)
- Confirm the result. The history is intact and the format has changed.
cd ../converted
git rev-parse --show-object-format
# sha256
git log --format='%H %s'
# 6b7d4f0bd580fe7030a11aecad57f6cbea14965fb64ea169e3d898f27c8fdae8 two
# 31b16d360e5e845a0895d08eafbcad0df340fb72cc1af4030bd02bf2b5e75e97 one
The commit subjects and order survive; the hashes do not, because they cannot.
- Restore the branch and tag names.
fast-importwrites the refs it was given, but check that your default branch, tags and any release refs are present before declaring success.
git branch -a
git tag --list
git fsck --full
git fsck --full should report no missing objects. If it does, the export was incomplete — most often because --all did not include a ref type you rely on, such as notes or a stash you expected to keep.
05Plan a rehearsed cutover, not a big bang
Rewrite once, then rehearse on a mirror. A workable sequence for a shared repository:
- Prepare a maintenance window and some downtime.
- Freeze pushes and convert a mirror clone into SHA-256.
- Grep the whole codebase and CI config for 40-character hex assumptions while contributors are paused.
- Push the converted repository as the new origin and force-push all branches.
- Have every clone re-clone rather than pull, because the old objects no longer match the new hashes.
That last step is unavoidable. Existing clones of the SHA-1 repository cannot fetch from the converted one, and a merge between them would duplicate history.
06Find everything that assumes a 40-character hash
This is where migrations actually fail, hours after the conversion looks successful. The hash is not just a Git detail; it leaks into tooling.
| Where | Assumption | Fix |
|---|---|---|
| CI cache keys | 40 hex characters | Key on the branch plus the full hash |
| Pinned GitHub Actions | Commit SHA in uses: | Re-pin after the rewrite; the old SHA is gone |
| Submodules | Parent repo pins child commit | Re-add the submodule from the converted child |
| Release notes | Links to commit URLs | Regenerate links from the new hashes |
| Scripts | substr($sha, 0, 7) | Seven characters still works, but dedupe risk grows |
| Signed commits and tags | Signature covers the object | Re-sign; signatures do not carry across the rewrite |
Search for the pattern rather than hoping to remember every location. A regular expression that matches a 40-character lowercase hex string is enough to start, and the regex tester is a fast way to tune it before you run it across a monorepo.
07Keep both formats working side by side
During the transition you will have SHA-1 and SHA-256 checkouts on the same machine, and git rev-parse --show-object-format tells you which one you are in. Set the format explicitly when you create tooling so a script does not silently assume the current directory is SHA-1:
git rev-parse --show-object-format
git -C /path/to/repo rev-parse --show-object-format
If a downstream system cannot be updated yet, keep a read-only SHA-1 mirror of the old repository for reference, but do not let anyone commit to it — diverging histories are worse than a delayed migration.
08Common errors and fixes
| Error | Cause | Fix |
|---|---|---|
mismatched algorithms | Fetching between object formats | Convert with fast-export / fast-import |
| History missing after import | --all did not cover the desired refs | Export specific refs or include notes and tags |
| CI can no longer check out a pinned SHA | Pinned to a pre-migration commit | Re-pin to the converted commit |
| Push rejected after cutover | Existing clone has SHA-1 objects | Re-clone into a fresh working copy |
git fsck reports dangling refs | Temporary refs from the import | Delete stale refs, then re-run git fsck |
The migration is not difficult because of the object format. It is difficult because a hash is an identity that leaks into caches, pins and integrations that never mention SHA-1. Convert the repository with a script, then treat every hardcoded hash as a bug to hunt before the window closes.
Compare the exported ref list before and after conversion to confirm nothing was dropped.
Diff CheckerFrequently asked questions
Can I convert a repository in place?
Not with a single command. Git has no in-place object-format conversion; the supported path is to rebuild the repository into a new one, which rewrites every object hash.
Does migration break commit hashes?
Yes, and that is the point. Every commit, tree and blob gets a new identifier, so tags, signed commits, submodule pins and external references all change.
Can a SHA-256 repository fetch from a SHA-1 remote?
Not in current Git. It fails with a mismatched algorithms error, which is why you convert through fast-export and fast-import rather than cloning.
Will my CI break?
Only where something assumes a 40-character hex string: cache keys, artifact paths, commit-pinned actions and deployment markers. Grep for 40-character patterns before cutover.
Is SHA-256 faster or slower?
SHA-256 objects are larger because the identifiers are longer, and hashing cost differs, but the practical effect is small compared with the migration work itself.
Related articles
- How to audit Git hooks for credential theft
Hooks run arbitrary code on checkout — worth auditing before a migration.
- Compare two texts with a diff checker
Verify the pre- and post-migration ref lists line by line.
Related tools
- Diff Checker
Spot ref or config changes between migrations.
- Regex Tester
Find 40-character hashes hardcoded in scripts.
- Base64 Encoder / Decoder
Inspect hashes embedded in tokens and payloads.