Portada de UrsaKit para la guía de migrar un repositorio Git de SHA-1 a SHA-256

Git calcula el hash de cada objeto —blob, tree, commit— y lo almacena bajo ese hash. Desde 2005 ese hash ha sido SHA-1, y los ataques de colisión publicados contra SHA-1 son la razón de que el proyecto añadiera un segundo formato de objetos. Git 3.0 tiene previsto hacer de SHA-256 el formato por defecto, lo que convierte una pregunta teórica en un problema de calendario para cualquiera que mantenga un repositorio grande.

Esta guía hace la conversión sobre un repositorio real, muestra el error exacto que aparece si pruebas el clonado obvio y enumera las suposiciones fijas que se rompen cuando un hash de 40 caracteres pasa a 64.

01Qué cambia cuando cambia el formato de objetos

Git es una base de datos direccionada por contenido: el nombre de un objeto es el hash de su contenido, así que la función de hash forma parte de la identidad del repositorio. De ahí salen dos consecuencias inmediatas.

  • Cada objeto recibe un nombre nuevo, así que cada hash de commit cambia.
  • Un repositorio SHA-256 y uno SHA-1 no pueden compartir objetos directamente, porque el mismo contenido hashea distinto en cada uno.

Ese segundo punto es el que se subestima. No puedes injertar un clon SHA-256 sobre una historia SHA-1 y hacer push; los grafos de objetos no son compatibles.

Fíjalo en tu plan: esto es una reescritura, no un ajuste

Cambiar el formato de objetos altera la identidad de cada commit. Todo lo anclado a un commit SHA-1 —actions de CI, etiquetas de release, commits de submódulos, registros de despliegue— apunta a un objeto que ya no existe en el repositorio convertido.

02Comprueba qué formato usan tus repositorios

Empieza midiendo, no adivinando. Todos los comandos siguientes se pueden ejecutar en un repositorio local.

git --version
# git version 2.50.1 (Apple Git-155)

git rev-parse --show-object-format
# sha1

rev-parse --show-object-format imprime el formato del repositorio en el que estás. Ejecútalo en todos tus checkouts para localizar los que siguen en SHA-1 y dimensiona el trabajo por número de objetos.

git count-objects -vH
git rev-list --all --count

Para una prueba rápida de corrección, hashea los mismos bytes en un repositorio SHA-1 y en uno SHA-256. Cambian tanto la longitud como el propio digest:

cd sha1-repo  && printf 'hello\n' | git hash-object --stdin
# ce013625030ba8dba906f756967f9e9ca394464a

cd sha256-repo && printf 'hello\n' | git hash-object --stdin
# 2cf8d83d9ee29543b34a87727421fdecb7e3f3a183d337639025de576db9ebb4

Crea un repositorio SHA-256 de pruebas para ver la diferencia sin tocar tu trabajo real:

git init --object-format=sha256 scratch
cd scratch && git commit --allow-empty -m init
git rev-parse HEAD
# a7bd209de3540f82c9736ffd0422615b8d8ebb7009ee32a6d9303f93f2807342

El id del commit tiene 64 caracteres hexadecimales, no 40. Esa única diferencia se propaga a cada script que valida o trunca un hash.

03El enfoque obvio falla: este es el error

Crear un repositorio SHA-256 y añadir el SHA-1 como remoto no funciona. El fetch da un error duro:

$ 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

No es un problema de permisos ni una opción ausente; las capas de transporte se niegan a mover objetos entre formatos. Eso descarta la estrategia que casi todo el mundo intenta primero.

No planifiques en torno a un clon entre formatos

git clone no tiene opción de formato de objetos, y el fetch entre formatos falla en el Git actual. Si una guía te dice que clones a SHA-256, describe una versión o un parche que todavía no existe.

04Convierte mediante fast-export y fast-import

La vía fiable es serializar la historia como texto y reproducirla en un repositorio nuevo, dejando que este hashee todo con SHA-256. Para eso existen exactamente fast-export y fast-import, y aquí se verifican sobre un repositorio de dos commits.

  1. Crea el repositorio destino con formato SHA-256.
git init --object-format=sha256 converted
  1. Transmite todas las refs del repositorio antiguo al nuevo. Ejecútalo desde el checkout de origen, apuntando a la ruta de destino.
git fast-export --all --signed-tags=verbatim | \
  (cd ../converted && git fast-import --quiet)
  1. Confirma el resultado. La historia está intacta y el formato ha cambiado.
cd ../converted
git rev-parse --show-object-format
# sha256
git log --format='%H %s'
# 6b7d4f0bd580fe7030a11aecad57f6cbea14965fb64ea169e3d898f27c8fdae8 two
# 31b16d360e5e845a0895d08eafbcad0df340fb72cc1af4030bd02bf2b5e75e97 one

Los mensajes y el orden de los commits sobreviven; los hashes no, porque no pueden.

  1. Restaura nombres de ramas y etiquetas. fast-import escribe las refs que recibe, pero comprueba que la rama por defecto, las etiquetas y cualquier ref de release estén antes de dar por hecho el éxito.
git branch -a
git tag --list
git fsck --full

git fsck --full no debería reportar objetos ausentes. Si lo hace, la exportación fue incompleta; casi siempre porque --all no incluía un tipo de ref que usas, como notas o un stash que esperabas conservar.

05Planifica un corte ensayado, no un big bang

Reescribe una vez y ensaya sobre un espejo. Una secuencia viable para un repositorio compartido:

  1. Prepara una ventana de mantenimiento con algo de inactividad.
  2. Congela los push y convierte un clon espejo a SHA-256.
  3. Busca en todo el código y en la configuración de CI suposiciones de 40 caracteres mientras el equipo está parado.
  4. Publica el repositorio convertido como nuevo origin y fuerza el push de todas las ramas.
  5. Haz que cada clon vuelva a clonar en lugar de hacer pull, porque los objetos antiguos ya no coinciden con los hashes nuevos.

Ese último paso es inevitable. Los clones SHA-1 existentes no pueden hacer fetch del repositorio convertido, y fusionar ambos duplicaría la historia.

06Localiza todo lo que asume un hash de 40 caracteres

Aquí es donde la migración falla de verdad, horas después de que la conversión parezca correcta. El hash no es solo un detalle de Git; se filtra al tooling.

DóndeSuposiciónSolución
Claves de caché de CI40 caracteres hexUsa rama más hash completo
GitHub Actions fijadasSHA de commit en uses:Vuelve a fijar tras la reescritura; el SHA antiguo ya no existe
SubmódulosEl repo padre fija el commit hijoReañade el submódulo desde el hijo convertido
Notas de releaseEnlaces a URLs de commitRegenera los enlaces con los hashes nuevos
Scriptssubstr($sha, 0, 7)Siete caracteres aún sirven, pero el riesgo de colisión crece
Commits y etiquetas firmadosLa firma cubre el objetoVuelve a firmar; las firmas no sobreviven a la reescritura

Busca el patrón en lugar de confiar en la memoria. Una expresión regular que case una cadena hexadecimal de 40 caracteres en minúsculas basta para empezar, y el probador de regex es una forma rápida de afinarla antes de lanzarla sobre un monorepo.

07Mantén ambos formatos funcionando a la vez

Durante la transición tendrás checkouts SHA-1 y SHA-256 en la misma máquina, y git rev-parse --show-object-format te dice en cuál estás. Especifica el formato al crear tooling para que un script no asuma en silencio que el directorio actual es SHA-1:

git rev-parse --show-object-format
git -C /path/to/repo rev-parse --show-object-format

Si un sistema posterior aún no se puede actualizar, conserva un espejo SHA-1 de solo lectura del repositorio antiguo como referencia, pero no dejes que nadie haga commit en él: historias divergentes son peor que un retraso.

08Errores comunes y soluciones

ErrorCausaSolución
mismatched algorithmsHacer fetch entre formatos de objetoConvierte con fast-export / fast-import
Falta historia tras el import--all no cubría las refs deseadasExporta refs concretas o incluye notas y etiquetas
El CI ya no puede hacer checkout de un SHA fijadoEstaba fijado a un commit previoVuelve a fijarlo al commit convertido
Push rechazado tras el corteEl clon existente tiene objetos SHA-1Vuelve a clonar en una copia nueva
git fsck reporta refs colgantesRefs temporales del importBorra refs obsoletas y repite git fsck

La migración no es difícil por el formato de objetos. Es difícil porque un hash es una identidad que se filtra a cachés, anclajes e integraciones que nunca mencionan SHA-1. Convierte el repositorio con un script y luego trata cada hash fijado como un bug que cazar antes de cerrar la ventana.

Preguntas frecuentes

¿Puedo convertir un repositorio en el mismo sitio?

No con un solo comando. Git no admite conversión in situ del formato de objetos; la vía soportada es reconstruir el repositorio en uno nuevo, lo que reescribe el hash de cada objeto.

¿La migración rompe los hashes de commit?

Sí, y esa es la idea. Cada commit, tree y blob recibe un identificador nuevo, así que etiquetas, commits firmados, anclajes de submódulos y referencias externas cambian.

¿Puede un repositorio SHA-256 hacer fetch de un remoto SHA-1?

Con el Git actual, no. Falla con un error de algoritmos incompatibles, por eso se convierte mediante fast-export y fast-import en lugar de clonar.

¿Se romperá mi CI?

Solo donde algo asuma una cadena hexadecimal de 40 caracteres: claves de caché, rutas de artefactos, actions fijadas a un commit y marcadores de despliegue. Busca el patrón antes del corte.

¿SHA-256 es más rápido o más lento?

Los objetos SHA-256 son mayores porque los identificadores son más largos, y el coste de hashing difiere, pero el efecto práctico es pequeño comparado con el trabajo de migración.

Volver a los artículos

Artículos relacionados

Herramientas relacionadas