¿Por qué git stash pop dice que no puede restaurar archivos sin seguimiento desde la entrada de alijo?

94

Tuve un montón de cambios por etapas y no por etapas y quería cambiar rápidamente a otra rama y luego volver.

Así que organicé mis cambios usando:

$ git stash push -a

(En retrospectiva, probablemente podría haber usado en --include-untrackedlugar de --all)

Luego, cuando fui a hacer estallar el alijo, recibí una gran cantidad de errores como:

$ git stash pop
foo.txt already exists, no checkout
bar.txt already exists, no checkout
...
Could not restore untracked files from stash entry

No parece haber ningún cambio restaurado desde el alijo.

También lo intenté, $ git stash branch temppero eso muestra los mismos errores.

Descubrí una forma de evitar esto que debía usar:

$ git stash show -p | git apply

Desastre evitado por ahora, pero esto plantea algunas preguntas.

¿Por qué ocurrió este error en primer lugar y cómo puedo evitarlo la próxima vez?

steinybot
fuente
29
Tuve que usar:git stash show -p | git apply --3
xmedeko
2
¡Lo único que funcionó para mí es el COMENTARIO ARRIBA!
Mehraj Malik
4
Gracias @xmedeko, ¿alguien puede notar la diferencia entre git stash show -p | git apply y git stash show -p | git aplicar --3?
Deepak Mohandas
3
Si te entra el pánico, simplemente una lista de sus archivos escondidos con git stash showy que los archivos de rescate, uno por uno: $ git show stash@{0}:your/stashed/file.txt > your_rescued_file.txt. Esto obtendrá el archivo del alijo y lo guardará con un nombre diferente. Ahora está seguro de experimentar con los métodos de rescate adecuados (vea las respuestas a continuación). Si las cosas van por mal camino, siempre tendrá sus archivos rescatados como último recurso.
Danijel
¡Vaya, gracias @xmedeko! Una vez más, su comentario fue lo único que funcionó, y fue muy simple. +1!
pcdev

Respuestas:

99

Como una pequeña explicación adicional, tenga en cuenta que git stashhace dos confirmaciones o tres confirmaciones. El valor predeterminado es dos; obtienes tres si usas cualquier ortografía de las opciones --allo --include-untracked.

Estas dos o tres confirmaciones son especiales en una forma importante: no están en ninguna rama. Git los ubica a través del nombre especial stash. 1 Sin embargo, lo más importante es lo que Git te permite, y te obliga , hacer con estas dos o tres confirmaciones. Para entender esto, necesitamos mirar qué hay en esas confirmaciones.

¿Qué hay dentro de un escondite?

Cada confirmación puede enumerar una o más confirmaciones principales . Estos forman un gráfico, donde las confirmaciones posteriores apuntan a las anteriores. El alijo normalmente contiene dos confirmaciones, que me gusta llamar ipara el contenido del índice / área de ensayo y wpara el contenido del árbol de trabajo. Recuerde también que cada confirmación contiene una instantánea. En una confirmación normal, esta instantánea se realiza a partir del contenido del índice / área de ensayo. ¡Así que el icompromiso es de hecho un compromiso perfectamente normal! Simplemente no está en ninguna rama:

...--o--o--o   <-- branch (HEAD)
           |
           i

Si está haciendo un alijo normal, el git stashcódigo lo hace wahora copiando todos sus archivos de árbol de trabajo rastreados (en un índice auxiliar temporal). Git establece que el primer padre de esta wconfirmación apunte a la HEADconfirmación y el segundo padre apunte a la confirmación i. Por último, se establece stashpara apuntar a este wcompromiso:

...--o--o--o   <-- branch (HEAD)
           |\
           i-w   <-- stash

Si agrega --include-untrackedo --all, Git realiza una confirmación adicional u, entre hacer iy w. El contenido de la instantánea uson aquellos archivos que no se rastrean pero no se ignoran ( --include-untracked), o archivos que no se rastrean incluso si se ignoran ( --all). Esta uconfirmación adicional no tiene padre, y luego, cuando se git stashrealiza w, establece wel tercer padre en esta uconfirmación, de modo que obtiene:

...--o--o--o   <-- branch (HEAD)
           |\
           i-w   <-- stash
            /
           u

Git también, en este punto, elimina los archivos del árbol de trabajo que terminaron en la uconfirmación (usando git cleanpara hacer eso).

Restaurar un alijo

Cuando vas a restaurar un alijo, tienes la opción de usarlo --indexo no. Esto le dice git stash apply(o cualquiera de los comandos que usa internamente apply, como pop) que debe usar el icompromiso para intentar modificar su índice actual. Esta modificación se realiza con:

git diff <hash-of-i> <hash-of-i's-parent> | git apply --index

(más o menos; hay un montón de detalles importantes que se interponen en el camino de la idea básica aquí).

Si omite --index, git stash applyignora por completo la iconfirmación.

Si el alijo tiene solo dos confirmaciones, git stash applyahora puede aplicar la wconfirmación. Hace esto llamando a git merge2 (sin permitirle confirmar o tratar el resultado como una fusión normal), usando la confirmación original en la que se hizo el alijo ( iel padre y wel primer padre) como base de fusión, wcomo el --theirscommit, y su compromiso actual (HEAD) como el objetivo de la fusión. Si la fusión tiene éxito, todo está bien, bueno, al menos Git así lo cree, y la git stash applymisma tiene éxito. Si solía git stash popaplicar el alijo, el código ahora elimina el alijo. 3 Si la fusión falla, Git declara que la aplicación ha fallado. Si usastegit stash pop, el código conserva el alijo y entrega el mismo estado de falla que para git stash apply.

Pero si tiene esa tercera confirmación, si hay una uconfirmación en el alijo que está aplicando, ¡entonces las cosas cambian! No hay opción para fingir que la uconfirmación no existe. 4 Git insiste en extraer todos los archivos de esa uconfirmación en el árbol de trabajo actual. Esto significa que los archivos no deben existir en absoluto o tener el mismo contenido que en la uconfirmación.

Para que eso suceda, puede usarlo git cleanusted mismo, pero recuerde que los archivos sin seguimiento (ignorados o no) no tienen otra existencia dentro de un repositorio Git, ¡así que asegúrese de que todos estos archivos se puedan destruir! O puede crear un directorio temporal y mover los archivos allí para su custodia, o incluso hacer otro git stash save -uo git stash save -a, ya que estos se ejecutarán git cleanautomáticamente. Pero eso te deja con otro ualijo de estilo con el que lidiar más tarde.


1 Esto es de hecho refs/stash. Esto es importante si crea una rama llamada stash: el nombre completo de la rama es refs/heads/stash, por lo que no están en conflicto. Pero no hagas eso: a Git no le importará, pero te confundirás. :-)

2 El git stashcódigo realmente se usa git merge-recursivedirectamente aquí. Esto es necesario por varias razones y también tiene el efecto secundario de asegurarse de que Git no lo trate como una fusión cuando resuelva conflictos y se comprometa.

3 Por eso recomiendo evitar git stash pop, a favor de git stash apply. Tiene la oportunidad de revisar lo que se aplicó y decidir si realmente se aplicó correctamente. Si no, todavía tienes tu alijo, lo que significa que puedes usarlo git stash branchpara recuperar todo perfectamente. Bueno, asumiendo la falta de ese molesto ucompromiso.

4 Realmente debería haber: git stash apply --skip-untrackedo algo. También debería haber una variante que signifique colocar todos esos uarchivos de confirmación en un nuevo directorio , por ejemplo git stash apply --untracked-into <dir>, quizás.

torek
fuente
8
Respuesta asombrosamente detallada. Gracias por tomarse el tiempo para escribir esto. ¡Aprendí mucho!
steinybot
Esta explicación merece una insignia de héroe. ¡Gracias por poner tantos detalles!
Lucas Fowler
1
@seelts Desafortunadamente, Git evoluciona continuamente, por lo que los libros se desactualizan rápidamente. Pero Git debe entenderse como un conjunto de herramientas (comandos que manipulan una confirmación de archivos, o manipulan el gráfico de confirmación, o lo que sea) que usted ensambla en lo que sea útil para usted , y no muchos libros parecen acercarse a eso. manera tampoco.
torek
3
No entiendo la solución al problema. ¿Es sólo agregó --index: git stash apply --index?
Danijel
1
Gracias @torek. Primero lo hice git stash save --all, luego lo hice de inmediato git stash apply, pero faltaban algunos archivos porque les cambié el nombre y luego los volví a crear (antes de guardarlos). Lo que ayudó fue: git checkout stash@{0} -- .ni siquiera me molestaré git checkout stash^3 -- .porque todo parece estar bien ahora. Es una pena que no tenga tiempo para entender realmente lo que estaba pasando. Gracias.
Danijel
100

Me las arreglé para recrear tu problema. Parece que si oculta los archivos sin seguimiento y luego crea esos archivos (en su ejemplo, foo.txty bar.txt), entonces tiene cambios locales en los archivos sin seguimiento que se sobrescribirán cuando realice la solicitud git stash pop.

Para solucionar este problema, puede utilizar el siguiente comando. Esto anulará cualquier cambio local no guardado, así que tenga cuidado.

git checkout stash -- .

Aquí hay más información que encontré en el comando anterior .

Daniel Smith
fuente
Mi árbol de trabajo estaba definitivamente limpio, aunque podría haber habido cambios en los archivos ignorados.
steinybot
1
Parece que usar --all/ -a incluirá archivos ignorados , por lo que puede ser relevante.
Daniel Smith
1
Asumiré que la misma lógica se aplica a los archivos ignorados y marcaré esto como la respuesta (aunque creo que el git merge --squash --strategy-option=theirs stashenfoque es mejor en este caso).
steinybot
¡De acuerdo, me gusta ese segundo enfoque! ¡Buena suerte con tu trabajo!
Daniel Smith
Esto fue útil pero no restauró los archivos sin seguimiento ; si tiene el mismo problema ( already exists, no checkout), verifique mi respuesta a continuación.
Erik Koopmans
25

Para ampliar la respuesta de Daniel Smith : ese código solo restaura los archivos rastreados , incluso si usó --include-untracked(o -u) al crear el alijo. El código completo requerido es:

git checkout stash -- .
git checkout stash^3 -- .
git stash drop

# Optional to unstage the changes (auto-staged by default).
git reset

Esto restaura completamente el contenido rastreado (en stash) y el contenido no rastreado (en stash^3), luego elimina el alijo. Algunas notas:

  • Tenga cuidado , ¡esto sobrescribirá todo con el contenido de su escondite!
  • Restaurar los archivos con git checkouthace que todos se conviertan en etapas automáticamente, por lo que he agregado git resetpara quitar la etapa de todo.
  • Algunos recursos usan stash@{0}y stash@{0}^3, en mis pruebas, funciona igual con o sin@{0}

Fuentes:

Erik Koopmans
fuente
1
¿Por qué eliminarías el alijo, por qué no dejarlo allí por un tiempo por razones de seguridad?
Danijel
@Danijel Seguro que puedes quedarte con el alijo, depende de tu caso de uso, supongo. Para mis propósitos, terminé con el alijo una vez que fue restaurado.
Erik Koopmans
3

Aparte de otras respuestas, hice un pequeño truco

  • Eliminó todos los archivos nuevos ( archivos ya existentes, por ejemplo, foo.txt y bar.txt en la pregunta)
  • git stash apply (puede usar cualquier comando, por ejemplo, aplicar, hacer estallar, etc.)
Sanjay Nishad
fuente
No funciona ... los archivos eliminados simplemente aparecen de nuevo en el alijo.
Aditya Rewari