Deshacer git reset: difícil con archivos no confirmados en el área de ensayo

109

Estoy intentando recuperar mi trabajo. Estúpidamente lo hice git reset --hard, pero antes de eso solo get add .hice y no hice git commit. ¡Por favor ayuda! Aquí está mi registro:

MacBookPro:api user$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)

#   modified:   .gitignore
...


MacBookPro:api user$ git reset --hard
HEAD is now at ff546fa added new strucuture for api

¿Es posible deshacer git reset --harden esta situación?

eistrati
fuente
@MarkLongair ¡hombre increíble! ¡Acabas de recuperar mi trabajo! ¡Escribí un script de Python para crear archivos de toda la salida! Agregaré el guión como respuesta
Chico
4
No 'estúpidamente' ... sino 'ingenuamente' ... ¡porque acabo de hacer LO MISMO!
Rosdi Kasim
Todavía podría ser estúpidamente ;-)
Duncan McGregor
Aquí hay un gran artículo sobre cómo revertir algo de esto. Va a necesitar algo de trabajo manual.
Jan Swart
@MarkLongair `` `buscar .git / objects / -type f -printf '% TY-% Tm-% Td% TT% p \ n' | sort `` funcionó para mí. También aparecen las fechas, comience a comprobar las manchas desde el final.
ZAky

Respuestas:

175

Debería poder recuperar cualquier archivo que haya agregado al índice (por ejemplo, como en su situación, con git add .), aunque puede ser un poco complicado. Para agregar un archivo al índice, git lo agrega a la base de datos del objeto, lo que significa que se puede recuperar siempre que no se haya realizado la recolección de basura. Hay un ejemplo de cómo hacer esto en la respuesta de Jakub Narębski aquí:

Sin embargo, probé eso en un repositorio de prueba y hubo un par de problemas, --cacheddebería ser --cache, y descubrí que en realidad no creaba el .git/lost-founddirectorio. Sin embargo, los siguientes pasos funcionaron para mí:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)")

Eso debería generar todos los objetos en la base de datos de objetos que no son accesibles por ninguna referencia, en el índice o mediante el reflog. La salida se verá así:

unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03

... y para cada una de esas manchas, puede hacer:

git show 907b308

Para generar el contenido del archivo.


¿Demasiada producción?

Actualización en respuesta al comentario de sehe a continuación:

Si encuentra que tiene muchas confirmaciones y árboles enumerados en la salida de ese comando, es posible que desee eliminar de la salida todos los objetos a los que se hace referencia de las confirmaciones no referenciadas. (Por lo general, puede volver a estas confirmaciones a través del reflog de todos modos; solo nos interesan los objetos que se han agregado al índice pero que nunca se pueden encontrar a través de una confirmación).

Primero, guarde la salida del comando, con:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > all

Ahora los nombres de objeto de esas confirmaciones inalcanzables se pueden encontrar con:

egrep commit all | cut -d ' ' -f 3

De modo que puede encontrar solo los árboles y objetos que se han agregado al índice, pero que no se han confirmado en ningún momento, con:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") \
  $(egrep commit all | cut -d ' ' -f 3)

Eso reduce enormemente la cantidad de objetos que tendrá que considerar.


Actualización: Philip Oakley a continuación sugiere otra forma de reducir la cantidad de objetos a considerar, que es simplemente considerar los archivos modificados más recientemente .git/objects. Puede encontrar estos con:

find .git/objects/ -type f -printf '%TY-%Tm-%Td %TT %p\n' | sort

(Encontré esa findinvocación aquí ). El final de esa lista podría verse así:

2011-08-22 11:43:43.0234896770 .git/objects/b2/1700b09c0bc0fc848f67dd751a9e4ea5b4133b
2011-09-13 07:36:37.5868133260 .git/objects/de/629830603289ef159268f443da79968360913a

En cuyo caso puede ver esos objetos con:

git show b21700b09c0bc0fc848f67dd751a9e4ea5b4133b
git show de629830603289ef159268f443da79968360913a

(Tenga en cuenta que debe eliminar el /al final de la ruta para obtener el nombre del objeto).

Mark Longair
fuente
antes de publicar mi respuesta, consideré incluir exactamente estos pasos. Sin embargo, al intentar esto con uno de mis repositorios locales, obtuve cientos de inalcanzables y no pude encontrar un enfoque adecuado para, digamos, ordenarlos por fecha de envío. Eso sería genial
mira el
3
¿No es posible echar un vistazo a las marcas de tiempo de los objetos para los objetos más recientes que son posteriores a su última confirmación? O me estoy perdiendo algo.
Philip Oakley
1
@Philip Oakley: gracias por esa sugerencia, agregué la sugerencia para encontrar los objetos modificados más recientemente en la base de datos de objetos.
Mark Longair
2
¡Amigo, eso es increíble! Eso salvó mi @ $$ justo ahora. Y aprendí algo de ciencia idiota. Mejor observe lo que agrega al índice ...
bowsersenior
1
no robar la luz de cal de nadie aquí. Pero solo como una nota, ya que me encontré con el mismo problema y pensé que había perdido todo mi trabajo. Para aquellos que usan un IDE , los IDE generalmente tienen una solución de recuperación de un clic. En el caso de PHPstorm, solo tenía que hacer clic derecho en el directorio y seleccionar mostrar historial local y luego volver al último estado válido.
Shalom Sam
58

Acabo de hacer un git reset --hardcompromiso y perdí uno. Pero conocía el hash de confirmación, así que pude git cherry-pick COMMIT_HASHrestaurarlo.

Hice esto a los pocos minutos de perder el compromiso, por lo que puede funcionar para algunos de ustedes.

Richard Saunders
fuente
5
Gracias, gracias, gracias. Logré recuperar un día de trabajo gracias a esta respuesta
Dace
21
Probablemente vale la pena mencionar que puede ver esos hash usando git reflog, por ejemplo, git reset --hard-> git reflog(mirando el hash HEAD @ {1}) y finalmentegit cherry-pick COMMIT_HASH
Javier López
2
Muchachos que lean esto, tengan en cuenta que esto solo funciona para un solo hash de confirmación, si hubo más de una confirmación, ¡no resolverá el problema de una vez!
Ain Tohvri
4
De hecho, puede restablecer el "avance". Entonces, si restableció varias confirmaciones, hacer otro 'git reset --hard <commit>' debería restaurar toda la cadena de confirmaciones hasta ese punto. (dado que aún no están en GC)
akimsko
6
El resumen para recuperar las confirmaciones perdidas con git reset --hard(suponiendo que no haya hecho nada más después de ese comando) esgit reset --hard @{1}
Ajedi32
13

¡Gracias a Mark Longair recuperé mis cosas!

Primero guardé todos los hashes en un archivo:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > allhashes

a continuación, los puse todos (eliminando el 'blob inalcanzable') en una lista y puse todos los datos en archivos nuevos ... tienes que elegir tus archivos y cambiarles el nombre de nuevo, lo que necesitas ... pero solo necesitaba unos pocos archivos ... espero que esto ayude a alguien ...

commits = ["c2520e04839c05505ef17f985a49ffd42809f",
    "41901be74651829d97f29934f190055ae4e93",
    "50f078c937f07b508a1a73d3566a822927a57",
    "51077d43a3ed6333c8a3616412c9b3b0fb6d4",
    "56e290dc0aaa20e64702357b340d397213cb",
    "5b731d988cfb24500842ec5df84d3e1950c87",
    "9c438e09cf759bf84e109a2f0c18520",
    ...
    ]

from subprocess import call
filename = "file"
i = 1
for c in commits:
    f = open(filename + str(i),"wb")
    call(["git", "show", c],stdout=f)
    i+=1
Chico
fuente
1
¡Si! Esto es exactamente lo que necesitaba. También me gusta el script de Python para recrear todos los archivos. Las otras respuestas me pusieron nervioso por perder mis datos con la recolección de basura, por lo que descargar los archivos es una victoria para mí :)
Eric Olson
1
Escribí este guión basado en esta respuesta. Funciona fuera de la caja: github.com/pendashteh/git-recover-index
Alexar
1
Me resulta más fácil de automatizar esta manera: mkdir lost; git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") | grep -Po '\s\S{40}$' | xargs -i echo "git show {} > lost/{}.blob" | sh. Los archivos terminarán enlost/*.blob
Matija Nalis
6

La solución de @ Ajedi32 en los comentarios funcionó para mí exactamente en esta situación.

git reset --hard @{1}

Tenga en cuenta que todas estas soluciones se basan en que no hay git gc, y algunas de ellas pueden causar una, por lo que comprimiría el contenido de su directorio .git antes de intentar algo para que tenga una instantánea a la que volver si no hay una. no funciona para ti.

Duncan McGregor
fuente
Tenía varios archivos sin comprometer. Luego comprometí 1 archivo e hice una git reset --hard, que estropeó mi repositorio de una manera desconocida. @Duncan, ¿qué hace @ {1}? y a que comentario te refieres? ¿Está reseteando un git reset?
alpha_989
Creo que @ {1} es una referencia relativa a la última confirmación, pero no soy un experto, solo informo lo que funcionó para mí
Duncan McGregor
3

Me encontré con el mismo problema, pero no había agregado los cambios al índice. Entonces, todos los comandos anteriores no me devolvieron los cambios deseados.

Después de todas las respuestas elaboradas anteriores, esta es una pista ingenua, pero puede ser que salve a alguien que no lo pensó primero, como lo hice yo.

Desesperado, intenté presionar CTRL-Z en mi editor (LightTable), una vez en cada pestaña abierta; afortunadamente, esto recuperó el archivo en esa pestaña, a su último estado antes del git reset --hard. HTH.

olange
fuente
1
Exactamente en la misma situación, no se pudo agregar el índice e hice un reinicio completo y ninguna de las soluciones funcionó. Este fue un movimiento INTELIGENTE, gracias hice Ctrl + Z y salvé mi día. Mi editor: SublimeText. ¡Uno de mi lado!
Vivek
1

Esto probablemente sea obvio para los profesionales de git, pero quería publicarlo ya que en mi búsqueda frenética no vi que esto surgiera.

Puse en escena algunos archivos, e hice una git reset --hard, me asusté un poco, y luego noté que mi estado mostraba que todos mis archivos aún estaban organizados, así como todas sus eliminaciones sin configurar.

En este punto, puede confirmar esos cambios por etapas, siempre que no organice sus eliminaciones. Después de esto, solo tienes que reunir el coraje para hacerlo git reset --harduna vez más, lo que te devolverá a esos cambios que habías realizado y que ahora acabas de cometer.

Nuevamente, esto probablemente no sea nada nuevo para la mayoría, pero espero que, dado que me ayudó y no encontré nada que sugiriera esto, pueda ayudar a otra persona.

ddoty
fuente
1

Dios mío, me tiré del pelo hasta que me encontré con esta pregunta y sus respuestas. Creo que la respuesta correcta y concisa a la pregunta formulada solo está disponible si reúne dos de los comentarios anteriores, así que aquí está todo en un solo lugar:

  1. Como lo menciona chilicuil, ejecute git reflogpara identificar allí el hash de confirmación al que desea volver

  2. Como lo mencionó akimsko, es probable que NO desee seleccionar con precisión a menos que solo haya perdido una confirmación, por lo que debe ejecutar git reset --hard <hash-commit-you-want>

Nota para los usuarios de egit Eclipse: no pude encontrar una manera de realizar estos pasos dentro de Eclipse con egit. Cerrar Eclipse, ejecutar los comandos anteriores desde una ventana de terminal y luego volver a abrir Eclipse funcionó bien para mí.

Lolo
fuente
0

Las soluciones anteriores pueden funcionar, sin embargo, hay formas más simples de recuperarse de esto en lugar de pasar por el gitcomplejo deshacer. Supongo que la mayoría de los restablecimientos de git ocurren en una pequeña cantidad de archivos, y si ya usa VIM, esta podría ser la solución más eficiente en el tiempo. La advertencia es que ya debe estar usando ViM'spersistent-undo, que debería usar de cualquier manera, porque le brinda la capacidad de deshacer un número ilimitado de cambios.

Estos son los pasos:

  1. En vim presione :y escriba el comando set undodir. Si ha persistent undoactivado en su .vimrc, mostrará un resultado similar a undodir=~/.vim_runtime/temp_dirs/undodir.

  2. En su repositorio, use git logpara averiguar la última fecha / hora en que realizó la última confirmación

  3. En su caparazón, navegue hasta su undodiruso cd ~/.vim_runtime/temp_dirs/undodir.

  4. En este directorio, use este comando para encontrar todos los archivos que ha cambiado desde la última confirmación

    find . -newermt "2018-03-20 11:24:44" \! -newermt "2018-03-23" \( -type f -regextype posix-extended -regex '.*' \) \-not -path "*/env/*" -not -path "*/.git/*"

    Aquí "2018-03-20 11:24:44" es la fecha y hora de la última confirmación. Si la fecha en la que lo hizo git reset --hardes "2018-03-22", use "2018-03-22", luego use "2018-03-23". Esto se debe a una peculiaridad de la búsqueda, donde el límite inferior es inclusivo y el límite superior es exclusivo. https://unix.stackexchange.com/a/70404/242983

  5. A continuación, vaya a cada uno de los archivos, ábralos en vim y haga un "20m anterior". Puede encontrar más detalles sobre "anterior" utilizando "h anterior". Aquí earlier 20msignifica volver al estado del archivo 20 minutos atrás, asumiendo que lo hizo git hard --reset, 20 minutos atrás. Repita esto con todos los archivos que se escupieron desde el findcomando. Estoy seguro de que alguien puede escribir un guión que combine estas cosas.

alpha_989
fuente
0

Estoy usando IntelliJ y pude simplemente revisar cada archivo y hacer:

Edit -> reload from disk

Afortunadamente, acababa de hacer lo git statuscorrecto antes de borrar mis cambios de trabajo, así que sabía exactamente lo que tenía que recargar.

Para descanso
fuente
0

Recordar la jerarquía de archivos y utilizar la técnica de Mark Longair con la modificación de Phil Oakley produce resultados espectaculares.

Básicamente, si al menos agregó los archivos al repositorio pero no los comprometió, puede restaurar utilizando interactivamente git show, inspeccionar el registro y usar la redirección de shell para crear cada archivo (recordando su ruta de interés).

HTH!

ainthu
fuente
0

Si ha revisado recientemente un, git diffentonces hay otra forma de recuperarse de incidentes como este, incluso cuando aún no haya realizado los cambios: si el resultado de git difftodavía está en el búfer de la consola, puede desplazarse hacia arriba, copiar y pegar el diff en un archivo y utilizar la patchherramienta para aplicar el diff a su árbol: patch -p0 < file. Este enfoque me salvó algunas veces.

Confusión
fuente