¿Cómo recuperar un alijo caído en Git?

1739

Frecuentemente uso git stashy git stash poppara guardar y restaurar cambios en mi árbol de trabajo. Ayer tuve algunos cambios en mi árbol de trabajo que había escondido y reventado, y luego hice más cambios en mi árbol de trabajo. Me gustaría volver y revisar los cambios ocultos de ayer, pero git stash popparece eliminar todas las referencias a la confirmación asociada.

Sé que si lo uso git stash, .git / refs / stash contiene la referencia de la confirmación utilizada para crear el alijo. Y .git / logs / refs / stash contiene todo el alijo. Pero esas referencias se han ido después git stash pop. Sé que el commit todavía está en mi repositorio en alguna parte, pero no sé qué era.

¿Hay una manera fácil de recuperar la referencia de confirmación de escondite de ayer?

Tenga en cuenta que esto no es crítico para mí hoy porque tengo copias de seguridad diarias y puedo volver al árbol de trabajo de ayer para obtener mis cambios. Estoy preguntando porque debe haber una manera más fácil!

Greg Hewgill
fuente
74
Nota para el futuro: si no quieres perder tus escondites cada vez que lo hagas git stash pop, puedes hacerlo en su git stash applylugar. Hace lo mismo, excepto que no elimina la referencia al alijo aplicado.
Kevin
3
Intenté todo aquí, no pude encontrar un alijo que ya había sido reventado. Muy contento por el jetbrains.com/help/idea/local-history.html
Juan Mendes
Tuve este problema Para actualizar mi repo, me encontré git stash, git pull -r upstream, git push -f origin, git stash pop, y el pop dijo "fatal: registro de árbitros / alijo está vacía". 😲 Intenté muchas de estas respuestas, nada funcionó. Cuando miré en .git / refs / stash , el SHA estaba allí. ¿Quizás un problema al marcar una unidad de red de Windows para la sincronización sin conexión? 🤷‍♂️
brianary

Respuestas:

2787

Una vez que conozca el hash de la confirmación de alijo que eliminó, puede aplicarlo como alijo:

git stash apply $stash_hash

O bien, puede crear una rama separada para ello con

git branch recovered $stash_hash

Después de eso, puedes hacer lo que quieras con todas las herramientas normales. Cuando termines, simplemente vuela la rama.

Encontrar el hash

Si acaba de abrirlo y el terminal sigue abierto, aún tendrá el valor hash impreso git stash popen la pantalla (gracias, Dolda).

De lo contrario, puede encontrarlo usando esto para Linux, Unix o Git Bash para Windows:

git fsck --no-reflog | awk '/dangling commit/ {print $3}'

... o usando Powershell para Windows:

git fsck --no-reflog | select-string 'dangling commit' | foreach { $bits = $_ -split ' '; echo $bits[2];}

Esto le mostrará todas las confirmaciones en los consejos de su gráfico de confirmación que ya no se hace referencia desde ninguna rama o etiqueta: cada confirmación perdida, incluida cada confirmación de ocultación que haya creado, estará en algún lugar de ese gráfico.

La forma más fácil de encontrar la confirmación de almacenamiento que desea es pasar esa lista a gitk:

gitk --all $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' )

... o vea la respuesta de emragins si usa Powershell para Windows.

Esto lanzará un navegador de repositorio que le mostrará todas las confirmaciones en el repositorio , independientemente de si es accesible o no.

Puede reemplazar gitkallí con algo como git log --graph --oneline --decoratesi prefiere un buen gráfico en la consola en lugar de una aplicación GUI separada.

Para detectar las confirmaciones de ocultación, busque mensajes de confirmación de esta forma:

        WIP en somebranch : commithash Algún mensaje de confirmación antiguo

Nota : El mensaje de confirmación solo estará en este formulario (comenzando con "WIP on") si no proporcionó un mensaje cuando lo hizo git stash.

Aristóteles Pagaltzis
fuente
49
Jaydel sacó las palabras de mi boca. Esta publicación salvó mi trabajo :) Me gustaría agregar: recordar la fecha en que trabajó en lo que perdió lo hace más fácil para navegar por gitk en busca de lo que está buscando.
Sridhar Sarnobat
44
@Codey: Porque PowerShell. No sé si MsysGit envía un binario AWK. Buscar en Google me dice que algo así %{ $_.Split(' ')[2]; }debería hacer el equivalente del comando {print $3}en ese awkcomando en PowerShell, pero no tengo un sistema de Windows para probar eso, y todavía necesita un equivalente para la /dangling commit/parte. De todos modos, solo corre git fsck --no-reflogy mira la salida. Desea los hashes de las líneas "colgando commit <commitID>".
Aristóteles Pagaltzis
77
Vale la pena mencionar que el mensaje de confirmación solo tendrá la cadena "WIP" si no proporcionó su propio mensaje al guardar (es decir, al hacerlo git stash save "<message>").
Samir Aguiar
12
Si sabe cuándo ocurrió la caída, puede usar esta línea para obtener la lista de confirmaciones pendientes al aumentar el tiempo: git fsck --no-reflog | awk '/dangling commit/ {print $3}' | xargs -L 1 git --no-pager show -s --format="%ci %H" | sortla última entrada es probablemente la que desea stash apply.
ris8_allo_zen0
3
git stash apply {ref}restaurado un alijo caído! gites tan genial que debería ser ilegal!
Tom Russell el
707

Si no cerró la terminal, solo mire la salida desde git stash popy tendrá el ID del objeto del alijo que se cayó. Normalmente se ve así:

$ git stash pop
[...]
Dropped refs/stash@{0} (2ca03e22256be97f9e40f08e6d6773c7d41dbfd1)

(Tenga en cuenta que git stash droptambién produce la misma línea).

Para recuperar ese alijo, solo corre git branch tmp 2cae03e, y lo obtendrás como una rama. Para convertir esto en un alijo, ejecuta:

git stash apply tmp
git stash

Tenerlo como una rama también le permite manipularlo libremente; por ejemplo, para seleccionarlo o fusionarlo.

Dolda2000
fuente
54
También se puede hacer git stash apply commitida continuación git stashpara obtener un nuevo escondite.
Matthew Flaschen el
32
Tenga en cuenta que si git fusiona automáticamente el alijo y tiene conflictos, no le mostrará el hash.
James
31
@ James: Por otra parte, si esos conflictos son el resultado de la ejecución git stash pop, tampoco se perderá el alijo, por lo que normalmente no es un problema.
Dolda2000
2
No había SHA en mi salida de git stash pop. :(
Tirar cuenta del
2
@Honey: De eso se trata git stash pop. Si desea aplicar el alijo sin soltarlo, use git stash applyen su lugar. Además, si desea aplicar un cambio a varias ramas, también puede elegir la confirmación.
Dolda2000
271

Solo quería mencionar esta adición a la solución aceptada. No fue inmediatamente obvio para mí la primera vez que probé este método (tal vez debería haber sido), pero para aplicar el alijo desde el valor hash, simplemente use "git stash apply":

$ git stash apply ad38abbf76e26c803b27a6079348192d32f52219

Cuando era nuevo en git, esto no estaba claro para mí, y estaba probando diferentes combinaciones de "git show", "git apply", "patch", etc.

Vadear
fuente
3
Tenga en cuenta que esto aplica (¡duh!) El alijo al árbol de trabajo actual. Si el árbol está sucio, es posible que desee usar una rama temporal o un alijo primero, aplique el alijo del SHA-1, vuelva a guardarlo y luego haga estallar el segundo al último alijo (llamado alijo @ {1}).
musiKk
111

Para obtener la lista de escondites que todavía están en su repositorio, pero que ya no son accesibles:

git fsck --unreachable | grep commit | cut -d" " -f3 | xargs git log --merges --no-walk --grep=WIP

Si le dio un título a su alijo, reemplace "WIP" -grep=WIPal final del comando con una parte de su mensaje, por ejemplo -grep=Tesselation.

El comando es grepping para "WIP" porque el mensaje de confirmación predeterminado para un alijo está en el formulario WIP on mybranch: [previous-commit-hash] Message of the previous commit.

Senthil A Kumar
fuente
1
echo 'git fsck - inalcanzable | grep commit | corte -d "" -f3 | xargs git log --merges --no-walk --grep = WIP '> / usr / local / bin / git-stashlog; chmod a + rx / usr / local / bin / git-stashlog # git stashlog
Erik Martino
O puede agregar esto a su .gitconfig como un alias (preceda el comando con a !).
asmeurer
Salvé mi tocino, bueno, en realidad no, pero me ahorró volver a codificar los días de trabajo, apreciado, dado que solo se me cayó recientemente, solo elegí el SHA superior de la salida de su comando, entonces ... git stash aplica SHA ... como se menciona en otras respuestas - muchas gracias
danday74
75

Acabo de construir un comando que me ayudó a encontrar mi confirmación de alijo perdido:

for ref in `find .git/objects | sed -e 's#.git/objects/##' | grep / | tr -d /`; do if [ `git cat-file -t $ref` = "commit" ]; then git show --summary $ref; fi; done | less

Esto enumera todos los objetos en el árbol .git / objects, localiza los que son de tipo commit, luego muestra un resumen de cada uno. Desde este punto, solo era cuestión de revisar los commits para encontrar un "WIP en el trabajo apropiado: 6a9bb2" ("work" es mi rama, 619bb2 es un commit reciente).

Observo que si uso "git stash apply" en lugar de "git stash pop", no tendría este problema, y ​​si uso "git stash save message ", la confirmación podría haber sido más fácil de encontrar.

Actualización: con la idea de Nathan, esto se acorta:

for ref in `git fsck --unreachable | grep commit | cut -d' ' -f3`; do git show --summary $ref; done | less
Greg Hewgill
fuente
41

git fsck --unreachable | grep commitdebería mostrar el sha1, aunque la lista que devuelve puede ser bastante grande. git show <sha1>mostrará si es la confirmación que desea.

git cherry-pick -m 1 <sha1> fusionará el commit en la rama actual.

Nathan Jones
fuente
37

Windows PowerShell equivalente usando gitk:

gitk --all $(git fsck --no-reflog | Select-String "(dangling commit )(.*)" | %{ $_.Line.Split(' ')[2] })

Probablemente hay una forma más eficiente de hacer esto en una tubería, pero esto hace el trabajo.

emraginas
fuente
1
Estoy muy agradecido por su respuesta
Виталий Шебаниц
32

Si desea recuperar un alijo perdido, primero debe encontrar el hash de su alijo perdido.

Como Aristóteles Pagaltzis sugirió que git fsckdebería ayudarte.

Personalmente utilizo mi log-allalias que me muestra cada commit (commits recuperables) para tener una mejor visión de la situación:

git log --graph --decorate --pretty=oneline --abbrev-commit --all $(git fsck --no-reflogs | grep commit | cut -d' ' -f3)

Puede hacer una búsqueda aún más rápida si solo está buscando mensajes "WIP on".

Una vez que conozca su sha1, simplemente cambie su registro de alijo para agregar el alijo anterior:

git update-ref refs/stash ed6721d

Probablemente prefiera tener un mensaje asociado para que -m

git update-ref -m "$(git log -1 --pretty=format:'%s' ed6721d)" refs/stash ed6721d

E incluso querrás usar esto como un alias:

restash = !git update-ref -m $(git log -1 --pretty=format:'%s' $1) refs/stash $1
Colin Hebert
fuente
2
Sin embargo, -d\\ debería ser -d\ (o incluso más claro -d' ')
joeytwiddle
Recibió un error: "fatal: argumento ambiguo 'colgando': revisión desconocida o ruta no en el árbol de trabajo".
Daniel Ryan
también necesita ajustar el subcomando con comillas git update-ref -m "$(git log -1 --pretty=format:'%s' ed6721d)" refs/stash ed6721
Andrei Shostik
18

Me gustó el enfoque de Aristóteles, pero no me gustó usar GITK ... ya que estoy acostumbrado a usar GIT desde la línea de comandos.

En cambio, tomé las confirmaciones colgantes y envié el código a un archivo DIFF para su revisión en mi editor de código.

git show $( git fsck --no-reflog | awk '/dangling commit/ {print $3}' ) > ~/stash_recovery.diff

Ahora puede cargar el archivo diff / txt resultante (está en su carpeta de inicio) en su editor txt y ver el código real y el SHA resultante.

Entonces solo usa

git stash apply ad38abbf76e26c803b27a6079348192d32f52219
Shaheen Ghiassy
fuente
17

Puede enumerar todas las confirmaciones inalcanzables escribiendo este comando en la terminal:

git fsck --unreachable

Comprobar hash de confirmación inalcanzable -

git show hash

Finalmente aplique si encuentra el artículo escondido -

git stash apply hash
Vivek Kumar
fuente
15

¿Por qué la gente hace esta pregunta? Porque aún no conocen ni entienden el reflog.

La mayoría de las respuestas a esta pregunta dan comandos largos con opciones que casi nadie recordará. Entonces las personas entran en esta pregunta y copian y pegan lo que creen que necesitan y lo olvidan casi inmediatamente después.

Aconsejaría a todos con esta pregunta que solo revisen el reflog (git reflog), no mucho más que eso. Una vez que vea esa lista de todas las confirmaciones, hay cientos de formas de averiguar qué confirmación está buscando y seleccionarla o crear una rama a partir de ella. En el proceso, habrá aprendido sobre el reflog y las opciones útiles para varios comandos básicos de git.

RobbyD
fuente
1
Hola robby Esto es relevante si estuvieras trabajando, te desviaran y necesites volver a donde lo dejaste hace un par de semanas solo para saber que no puedes encontrar tu trabajo escondido; probablemente se perdió en algún otro lugar estaban haciendo. reflog es genial si es historia reciente, pero no por brechas de mucho tiempo.
emragins
1
Hola emragins, estoy de acuerdo, pero este fue exactamente el caso de uso del OP. No estoy seguro de cómo se comportarían los otros comandos que se publican aquí, pero mi idea sería que también dejarían de funcionar una vez que se limpie la referencia a su confirmación oculta.
RobbyD
1
Hmm ... el escenario anterior fue lo que me trajo a esta pregunta, y sé que pasaron al menos un par de semanas, o incluso más cerca de un mes entre cuando (sin saberlo) perdí mi alijo y cuando pude recuperarlo.
emragins
15

En OSX con git v2.6.4, simplemente ejecuto git stash drop accidentalmente, luego lo encontré siguiendo los pasos a continuación

Si sabes el nombre del alijo, usa:

$ git fsck --unreachable | grep commit | cut -c 20- | xargs git show | grep -B 6 -A 2 <name of the stash>

de lo contrario, encontrará la identificación del resultado manualmente con:

$ git fsck --unreachable | grep commit | cut -c 20- | xargs git show

Luego, cuando encuentre el commit-id, simplemente presione el git stash apply {commit-id}

Espero que esto ayude a alguien rápidamente

Can Tecim
fuente
12

Quiero agregar a la solución aceptada otra buena forma de realizar todos los cambios, cuando no tienes gitk disponible o no hay X para la salida.

git fsck --no-reflog | awk '/dangling commit/ {print $3}' > tmp_commits

for h in `cat tmp_commits`; do git show $h | less; done

Luego obtienes todas las diferencias para los hashes que se muestran uno tras otro. Presione 'q' para llegar a la siguiente diferencia.

Phil
fuente
12

No pude obtener ninguna de las respuestas para trabajar en Windows en una simple ventana de comandos (Windows 7 en mi caso). awk, grepy Select-stringno fueron reconocidos como comandos. Así que probé un enfoque diferente:

  • primer intento: git fsck --unreachable | findstr "commit"
  • copiar la salida al bloc de notas
  • buscar reemplazar "confirmación inalcanzable" con start cmd /k git show

se verá algo así:

start cmd /k git show 8506d235f935b92df65d58e7d75e9441220537a4 start cmd /k git show 44078733e1b36962571019126243782421fcd8ae start cmd /k git show ec09069ec893db4ec1901f94eefc8dc606b1dbf1 start cmd /k git show d00aab9198e8b81d052d90720165e48b287c302e

  • guardar como un archivo .bat y ejecutarlo
  • el script abrirá un montón de ventanas de comandos, mostrando cada confirmación
  • si encontraste el que estás buscando, ejecuta: git stash apply (your hash)

Puede que no sea la mejor solución, pero funcionó para mí

Koen
fuente
Puedes usar git bash incluso en Windows. En git bash tiene todas las herramientas de línea de comandos (unixoides) que necesita.
Adrian W
10

La respuesta aceptada por Aristóteles mostrará todas las confirmaciones accesibles, incluidas las confirmaciones que no son de tipo oculto. Para filtrar el ruido:

git fsck --no-reflog | \
awk '/dangling commit/ {print $3}' | \
xargs git log --no-walk --format="%H" \
  --grep="WIP on" --min-parents=3 --max-parents=3

Esto solo incluirá confirmaciones que tienen exactamente 3 confirmaciones principales (que tendrá un alijo), y cuyo mensaje incluye "WIP on".

Tenga en cuenta que si guardó su alijo con un mensaje (por ejemplo git stash save "My newly created stash"), esto anulará el mensaje predeterminado "WIP on ...".

Puede mostrar más información sobre cada confirmación, por ejemplo, mostrar el mensaje de confirmación o pasarlo a git stash show:

git fsck --no-reflog | \
awk '/dangling commit/ {print $3}' | \
xargs git log --no-walk --format="%H" \
  --grep="WIP on" --min-parents=3 --max-parents=3 | \
xargs -n1 -I '{}' bash -c "\
  git log -1 --format=medium --color=always '{}'; echo; \
  git stash show --color=always '{}'; echo; echo" | \
less -R
Brad Feehan
fuente
6

Mi favorito es este de una sola línea:

git log --oneline  $( git fsck --no-reflogs | awk '/dangling commit/ {print $3}' )

Esta es básicamente la misma idea que esta respuesta, pero mucho más corta. Por supuesto, aún puede agregar --graphpara obtener una visualización en forma de árbol.

Cuando haya encontrado la confirmación en la lista, solicite con

git stash apply THE_COMMIT_HASH_FOUND

Para mí, el uso --no-reflogsreveló la entrada de alijo perdida, pero --unreachable(como se encuentra en muchas otras respuestas) no lo hizo.

Ejecútelo en git bash cuando esté bajo Windows.

Créditos: los detalles de los comandos anteriores se toman de https://gist.github.com/joseluisq/7f0f1402f05c45bac10814a9e38f81bf

Adrian W
fuente
5

Lo recuperó utilizando los siguientes pasos:

  1. Identifique el código hash de escondite eliminado:

    gitk --all $ (git fsck --no-reflog | awk '/ dangling commit / {print $ 3}')

  2. Cherry Pick the Stash:

    git cherry-pick -m 1 $ stash_hash_code

  3. Resolver conflictos si los hay usando:

    git mergetool

Además, es posible que tenga problemas con el mensaje de confirmación si está utilizando gerrit. Guarde sus cambios antes de seguir las siguientes alternativas:

  1. Use el restablecimiento completo a la confirmación anterior y luego vuelva a comprometer este cambio.
  2. También puede esconder el cambio, volver a redactar y volver a comprometerse.
Abhijeet
fuente
@ miva2 su edición eliminó el enlace a la respuesta más correcta en esta pregunta. Agregar el enlace nuevamente en el comentario stackoverflow.com/questions/89332/…
Abhijeet
4

Lo que vine a buscar aquí es cómo recuperar el alijo, independientemente de lo que haya verificado. En particular, había escondido algo, luego revisé una versión anterior, luego lo expulsé, pero el alijo no funcionaba en ese momento anterior, por lo que el alijo desapareció; No podía simplemente hacer git stashque volviera a la pila. Esto funcionó para mí:

$ git checkout somethingOld
$ git stash pop
...
nothing added to commit but untracked files present (use "git add" to track)
Dropped refs/stash@{0} (27f6bd8ba3c4a34f134e12fe69bf69c192f71179)
$ git checkout 27f6bd8ba3c
$ git reset HEAD^    # Make the working tree differ from the parent.
$ git stash # Put the stash back in the stack.
Saved working directory and index state WIP on (no branch): c2be516 Some message.
HEAD is now at c2be516 Some message.
$ git checkout somethingOld # Now we are back where we were.

En retrospectiva, debería haber estado usando git stash applyno git stash pop. Estaba haciendo una bisecty tenía un pequeño parche que quería aplicar en cada bisectpaso. Ahora estoy haciendo esto:

$ git reset --hard; git bisect good; git stash apply
$ # Run tests
$ git reset --hard; git bisect bad; git stash apply
etc.
Ben
fuente
¿Es esta una respuesta o la continuación de la pregunta?
Alex Brown
Un poco de ambos. Encontré esta página porque perdí un alijo y estaba tratando de recuperarlo. El caso de uso para mí es hacer una bisección en la que quiero aplicar un cambio antes de probar en cada paso. Aprendí de la manera difícil que no puedes simplemente reventar, probar, esconder, bisecar porque eso puede dejar una confirmación diferente en el escondite, por lo tanto stash apply.
Ben