Recuperación de Git: "el file de object está vacío". Cómo recrear treees?

Nota: No tengo ningún clon anterior a la corrupción de este repository. Creo que mi situación es diferente a las otras que se describen aquí, porque me falta un tree, no una burbuja.

Que pasó:

Cuando intenté clonar un repository sobre LAN (a través de SSH), Git devolvió un error que decía que el repository está dañado:

remote: error: object file ./objects/2e/223ce259e9e33998d434acc778bc64b393d5d4 is empty remote: fatal: loose object 2e223ce259e9e33998d434acc778bc64b393d5d4 (stonetworking in ./objects/2e/223ce259e9e33998d434acc778bc64b393d5d4) is corrupt error: git upload-pack: git-pack-objects died with error. fatal: git upload-pack: aborting due to possible repository corruption on the remote side. remote: aborting due to possible repository corruption on the remote side. 

He encontrado en alguna parte que git fsck se puede usar para diagnosticar la corrupción, pero no me dice nada nuevo:

 git fsck --full error: object file ./objects/2e/223ce259e9e33998d434acc778bc64b393d5d4 is empty fatal: loose object 2e223ce259e9e33998d434acc778bc64b393d5d4 (stonetworking in ./objects/2e/223ce259e9e33998d434acc778bc64b393d5d4) is corrupt 

Intenté clonar el repository localmente (usando --no-hardlinks ) para ver qué sucede, pero obtuve exactamente los mismos resultados.

Luego me encontré con esta pregunta , y el tipo que respondió simplemente eliminó el file vacío (paso 3), así que hice esto (es decir, he eliminado el file 223ce259e9e33998d434acc778bc64b393d5d4 de los objects/2e/ del subdirectory objects/2e/ ).

git fsck nuevamente, y veo:

 Checking object directories: 100% (256/256), done. broken link from tree 838e437f371c652fa4393d25473ce21cbf697d7a to tree 2e223ce259e9e33998d434acc778bc64b393d5d4 dangling commit 54146bc0dc4eb3eede82a0405b749e05c11c5522 missing tree 2e223ce259e9e33998d434acc778bc64b393d5d4 dangling commit 864864feec207786b84158e526b2faec7799fd4e dangling blob d3cfd7cc7718d5b76df70cf9865db01c25181bfb 

Entonces, ahora hay un problema con el tree 838e437f37 . Eso no es lo que le sucedió al tipo mencionado anteriormente, así que busqué en Google y encontré información de Linus .

Entonces, hice git ls-tree 838e437f371c652fa4393d25473ce21cbf697d7a y en la salida había una lectura de línea:

 040000 tree 2e223ce259e9e33998d434acc778bc64b393d5d4 moje 

Ahora, "moje" es un directory (a diferencia del ejemplo que explicó Linus, que era un file). Supongo que es por eso que el siguiente paso sugerido por Linus, git hash-object moje fatal: Unable to hash moje .

Pero de todos modos, había una pequeña posibilidad de que fuera lo que necesitaba, así que fui a search más. git log --raw --all --full-history -- moje/ y de acuerdo con la guía de Linus, debería haber una confirmación que enumera 2e223 como un hash SHA-2 de algún contenido, pero no hay ninguno. Y la list termina con

 fatal: unable to read source tree (2e223ce259e9e33998d434acc778bc64b393d5d4) 

Traté de ver el último compromiso enumerado antes de ese error, pero no encontré este hash. He visto esto , pero no me ayudó, probablemente porque hubo algunos cambios entre la versión problemática y el estado actual del tree de trabajo.

Hay una cosa que puede ser importante: dentro de moje/ hay un directory cli/ que es un repository de Git en sí mismo (un submodule). He buscado el hash SHA-2 problemático allí, pero no lo he encontrado.

¿Que debería hacer?

Use este command para get una list de las confirmaciones que contienen su tree faltante:

 git rev-list --all | xargs -l -I '{}' sh -c 'if git ls-tree -rt {} > /dev/null 2>&1 ; then true; else git log --oneline -1 {}; git ls-tree -r -t {} | tail -1; fi' 

Ahora necesita recrear el tree que falta, colocando los mismos contenidos exactamente allí que tenía allí en ese momento y luego agregar ese tree al repository. La forma más sencilla de hacerlo es, simplemente, recrear el contenido y luego enviarlo al repository (puede eliminarlo luego).

El command (sugerido por Chronial)

 git rev-list --all | xargs -l -I '{}' sh -c 'if git ls-tree -rt {} > /dev/null 2>&1 ; then true; else git log --oneline -1 {}; git ls-tree -r -t {} | tail -1; fi' 

devolvió el primer commit que dependía del object 2e223ce faltaba – su hash SHA-2 era 499b8fb . Su padre estaba bien (pude ver su contenido, 89b0fc4 , etc.), y también pude ver el siguiente compromiso después del roto ( 89b0fc4 ).

Ahora necesitaba ver qué cambios ocurrieron entre estos dos commits "buenos" – eso fue fácil: git diff 499b8fb~ 89b0fc4 devuelto

 diff --git a/somefile b/somefile deleted file mode 100644 index f5d1e1e..0000000 --- a/somefile +++ /dev/null @@ -1,79 +0,0 @@ [ contents of the deleted "somefile"... ] diff --git a/moje/cli b/moje/cli index 640a825..c0b1a24 160000 --- a/moje/cli +++ b/moje/cli @@ -1 +1 @@ -Subproject commit 640a825cd671dfba83601d6271e7e027665eaca8 +Subproject commit c0b1a24aa246289831ec7db3a8596376db1f625a 

Ahora sé que entre el padre del commit erróneo y el commit bueno se eliminó el file somefile , y HEAD del submodule cambió de 640a825 a c0b1a24 . Fui al repository de submodules y pregunté qué cometidos ocurrieron entre esos dos:

 git log --oneline 640a825..c0b1a24 

que regresó

 c0b1a24 <commit message> 8be9433 <commit message> 02564e1 <commit message> 

Ahora sabía que ocurrieron cuatro cosas entre 499b8fb~ y 89b0fc4 :

  • somefile fue eliminado
  • /moje/cli HEAD se cambió de 640a825 a 02564e1
  • /moje/cli HEAD se cambió de 02564e1 a 8be9433
  • /moje/cli HEAD se cambió de 8be9433 a c0b1a24

No sabía qué parte sucedió en 499b8fb (la confirmación incorrecta) y cuál en 89b0fc4 . Pero afortunadamente no hay tantas posibilidades, así que solo intenté cada una de ellas. Con cada combinación hice una confirmación para que Git calculara los objects apropiados y los almacenara en la database. Resultó que cuando /moje/cli HEAD estaba en 8be9433 , el git commit dio como resultado la creación del object 2e223ce faltaba – ¡hurra!

Nota: si está teniendo una situación similar y está hurgando para ver qué compromisos son buenos y qué puede decirle Git sobre ellos, recuerde que el hecho de poder checkout un compromiso y show son dos cosas diferentes. Por ejemplo, inicialmente pensé que si git show somesha arroja un error, significa que somesha commit está dañado y no puedo usarlo para nada. Resultó ser falso: mientras que el git show 89b0fc4 devolvió un error, pude git checkout 89b0fc4 y también git diff 499b8fb~ 89b0fc4 .

Supongo que es porque git show somesha muestra qué cambios introduce somesha , y para eso Git necesita leer el contenido de la confirmación anterior (en este caso, una versión corrupta). Aparentemente, Git no necesita mirar el compromiso anterior para ver uno.

( Logré hacer esto gracias a la respuesta de Chronial, ¡ felicitaciones para él! Me aconsejaron que publicara esto como mi propia respuesta).