El escondite solo realizó cambios en git: ¿es posible?

368

¿Hay alguna forma en que pueda esconder solo mis cambios por etapas? El escenario con el que estoy teniendo problemas es cuando he trabajado en varios errores en un momento dado y tengo varios cambios sin clasificar. Me gustaría poder organizar estos archivos individualmente, crear mis archivos .patch y guardarlos hasta que se apruebe el código. De esta manera, cuando se aprueba, puedo esconder toda mi sesión (actual), reventar ese error y presionar el código.

¿Voy por esto de la manera incorrecta? ¿Estoy malinterpretando cómo git puede funcionar de otras maneras para simplificar mi proceso?

MrDuk
fuente
Sí, probablemente estás haciendo las cosas mal para entrar en esta situación. Sigue siendo una pregunta útil. Realmente debería esconderse o ramificarse antes de comenzar con la próxima solución. La respuesta tangencial stackoverflow.com/a/50692885 es probablemente una mejor manera de manejar esto en git. Jugar con el alijo a menudo hace cosas extrañas en mi área de trabajo si extraigo los compromisos de arriba.
Samuel Åslund

Respuestas:

472

Sí, es posible con DOUBLE STASH

  1. Organice todos sus archivos que necesita guardar.
  2. Ejecutar git stash --keep-index. Este comando creará un alijo con TODOS sus cambios (en etapas y sin etapas ), pero dejará los cambios en etapas en su directorio de trabajo (todavía en estado).
  3. correr git stash push -m "good stash"
  4. Ahora su SOLO"good stash" tiene archivos organizados .

Ahora, si necesita archivos sin clasificar antes de la ocultación, simplemente aplique la primera ocultación ( la creada con--keep-index ) y ahora puede eliminar los archivos que guardó "good stash".

Disfrutar

Bartłomiej Semańczyk
fuente
Guarda los cambios en los submódulos aunque no estén organizados. ¿Hay alguna forma de evitar esto?
Rluks
1
Esto de alguna manera dejó todos los archivos nuevos (incluso en escena).
Aurimas
8
@Aurimas, para guardar nuevos archivos, debe usar el -uinterruptor.
Gyromite
2
cuando vuelva a aplicar el primer alijo y vuelva a cambiar todo, mientras que puede que solo le interese la git stash apply --indexopción de uso de cambios sin etapas . Esto intentará mantener su estado no (en etapas). Ahora es más fácil eliminar los cambios no deseados del árbol de trabajo.
otomo
Si bien no necesitaba hacer exactamente lo que decía esta respuesta, conocer el indicador --keep-index fue muy útil.
Aaron Krauss el
129

Con el último git puedes usar la --patchopción

git stash push --patch  

git stash save --patch   # for older git versions

Y git le pedirá cada cambio en sus archivos para agregar o no al escondite.
Solo respondes yon

UPD
Alias ​​para DOBLE STASH :

git config --global alias.stash-staged '!bash -c "git stash --keep-index; git stash push -m "staged" --keep-index; git stash pop stash@{1}"'

Ahora puede organizar sus archivos y luego ejecutarlos git stash-staged.
Como resultado, sus archivos almacenados se guardarán en el alijo .

Si no desea mantener los archivos almacenados y desea moverlos a un alijo. Luego puede agregar otro alias y ejecutar git move-staged:

git config --global alias.move-staged '!bash -c "git stash-staged;git commit -m "temp"; git stash; git reset --hard HEAD^; git stash pop"'
Eugen Konkov
fuente
17
Técnicamente no responde la pregunta, pero es una técnica realmente agradable que logra un escondite selectivo.
alexreardon
66
De acuerdo, esto está bien, pero la idea aquí con la pregunta es que YA he hecho todo este trabajo de organizar los cambios con los que quiero hacer algo (aparentemente originalmente para comprometer, pero ahora quiero guardar), no solo buscando hazlo todo de nuevo.
Steven Lu
44
no funciona para archivos recién creados (solo funciona en los archivos modificados)
Derek Liang
@DerekLiang: los archivos recién creados no se rastrean en absoluto. Probablemente deberías marcar la -u|--include-untrackedopción degit-stash
Eugen Konkov
2
De los documentos : " guardar : esta opción está en desuso a favor de git stash push . Se diferencia de 'stash push' en que no puede tomar pathpecs, y cualquier argumento que no sea una opción forma el mensaje".
Borjovsky
53

Hice un guión que oculta solo lo que está escenificado actualmente y deja todo lo demás. Esto es increíble cuando empiezo a hacer muchos cambios no relacionados. Simplemente ponga en escena lo que no está relacionado con la confirmación deseada y oculte solo eso.

(Gracias a Bartłomiej por el punto de partida)

#!/bin/bash

#Stash everything temporarily.  Keep staged files, discard everything else after stashing.
git stash --keep-index

#Stash everything that remains (only the staged files should remain)  This is the stash we want to keep, so give it a name.
git stash save "$1"

#Apply the original stash to get us back to where we started.
git stash apply stash@{1}

#Create a temporary patch to reverse the originally staged changes and apply it
git stash show -p | git apply -R

#Delete the temporary stash
git stash drop stash@{1}
Joe
fuente
77
Agregaría
Petr Bela
3
¡Esto es genial! Lo modifiqué
pedirle
1
Esto no funciona en absoluto con git 2.23.0.
Thnee
Gracias, voté y lo convertí en un alias aquí: stackoverflow.com/a/60875067/430128 .
Raman
36

TL; DR Solo agregue -- $(git diff --staged --name-only)para su <pathspec>parámetro git

Aquí hay una línea simple:

git stash -- $(git diff --staged --name-only)

Y para agregar un mensaje simplemente:

git stash push -m "My work in progress" -- $(git diff --staged --name-only)

Probado en v2.17.1 y v2.21.0.windows.1

Limitaciones:

  • Tenga en cuenta que esto esconderá todo, si no tiene archivos almacenados.
  • Además, si tiene un archivo que está parcialmente en etapas (es decir, solo algunas líneas cambiadas, están puestas en escena, mientras que otras líneas cambiadas no lo están), entonces el archivo completo se guardará (incluidas las líneas sin etapas).
Somo S.
fuente
66
Creo que esta es la mejor opción en la situación descrita: ¡fácil de entender y sin magia negra!
Luis
1
Esto es bastante bueno. ¡Terminé creando un alias con eso!
Kalpesh Panchal
mantengan los votos en camino 😉
Somo S.
@KalpeshPanchal, ¿puedes compartir tu alias? No estoy seguro de cómo escapar, por lo que no lo está interpretando correctamente.
Igor Nadj
2
@IgorNadj ¡Seguro! Aquí está: github.com/panchalkalpesh/git-aliases/commit/…
Kalpesh Panchal
15

Para lograr lo mismo ...

  1. Organice solo los archivos en los que desea trabajar.
  2. git commit -m 'temp'
  3. git add .
  4. git stash
  5. git reset HEAD~1

Auge. Los archivos que no quieres están guardados. Los archivos que desea están listos para usted.

Miguel
fuente
3
Esta es fácilmente la mejor respuesta y la más fácil de recordar
Kevin
9

En este escenario, prefiero crear nuevas sucursales para cada problema. Uso un prefijo temp /, así que sé que puedo eliminar estas ramas más tarde.

git checkout -b temp/bug1

Organice los archivos que corrigen el bug1 y confírmelos.

git checkout -b temp/bug2

Luego, puede seleccionar los commits de las ramas respectivas según sea necesario y enviar una solicitud de extracción.

Shamps
fuente
2
Si bien es bueno conocer los elegantes sonidos de escondite, en la práctica esto parece un enfoque que es menos probable que critique.
ryanjdillon
1
Use "git cherry-pick tmpCommit" para recuperar el commit temporal de nuevo con un merge-commit o "git merge tmpCommit" + "git reset HEAD ^" para obtener los cambios sin el commit.
Samuel Åslund
1
Como muestra esta respuesta, a veces es mejor preguntar directamente qué quiere lograr en lugar de cómo lograrlo con la técnica dada. Las ramas temporales y la selección de cerezas son útiles en situaciones complicadas.
Guney Ozsan el
si por etapas un archivo parcial, necesitará para guardar sus cambios antes de regresar a la rama original y apareciendo de nuevo
Ene-GLX
6

¿Por qué no confirma el cambio para un error determinado y crea un parche a partir de ese compromiso y su predecesor?

# hackhackhack, fix two unrelated bugs
git add -p                   # add hunks of first bug
git commit -m 'fix bug #123' # create commit #1
git add -p                   # add hunks of second bug
git commit -m 'fix bug #321' # create commit #2

Luego, para crear los parches apropiados, use git format-patch:

git format-patch HEAD^^

Esto creará dos archivos: 0001-fix-bug-123.patchy0002-fix-bug-321.patch

O puede crear ramas separadas para cada error, de modo que pueda fusionar o volver a crear correcciones de errores individualmente, o incluso eliminarlas, si no funcionan.

knittl
fuente
2

git stash --keep-index es una buena solución ... excepto que no funcionó correctamente en las rutas que se han eliminado, lo que se ha solucionado en Git 2.23 (Q3 2019)

Ver commit b932f6a (16 de julio de 2019) por Thomas Gummerer ( tgummerer) .
(Fusionada por Junio ​​C Hamano - gitster- en commit f8aee85 , 25 jul 2019)

stash: arregla el manejo de archivos eliminados con --keep-index

git stash push --keep-index se supone que mantiene todos los cambios que se han agregado al índice, tanto en el índice como en el disco.

Actualmente, esto no se comporta correctamente cuando se elimina un archivo del índice.
En lugar de mantenerlo eliminado en el disco, ** - keep-index actualmente restaura el archivo. **

Solucione ese comportamiento usando ' git checkout' en modo sin superposición que puede restaurar fielmente el índice y el árbol de trabajo.
Esto también simplifica el código.

Tenga en cuenta que esto sobrescribirá los archivos sin seguimiento si el archivo sin seguimiento tiene el mismo nombre que un archivo que se ha eliminado en el índice.

VonC
fuente
2

Esconder solo el índice (cambios por etapas) en Git es más difícil de lo que debería ser. Encontré que la respuesta de @Joe funciona bien, y convertí una pequeña variación en este alias:

stash-index = "!f() { \
  git stash push --quiet --keep-index -m \"temp for stash-index\" && \
  git stash push \"$@\" && \
  git stash pop --quiet stash@{1} && \
  git stash show -p | git apply -R; }; f"

Empuja tanto la puesta en escena y cambios unstaged en un escondite temporal, dejando el cambio por etapas solos. Luego empuja los cambios por etapas en el alijo, que es el alijo que queremos mantener. Los argumentos pasados ​​al alias, como --message "whatever"se agregarán a este comando de ocultación. Finalmente, muestra el alijo temporal para restaurar el estado original y elimina el alijo temporal, y luego finalmente "elimina" los cambios escondidos del directorio de trabajo a través de una aplicación de parche inverso.

Para el problema opuesto de esconder solo los cambios no escalonados (alias stash-working), vea esta respuesta .

Raman
fuente
1

¿Es absolutamente necesario trabajar en varios errores a la vez? Y por "a la vez", quiero decir "tener archivos editados para múltiples errores al mismo tiempo". Porque a menos que lo necesite absolutamente, solo trabajaría en un error a la vez en su entorno. De esa manera, puede usar sucursales locales y rebase, lo cual me parece mucho más fácil que administrar un alijo / escenario complejo.

Digamos que master está en commit B. Ahora trabaje en el bug # 1.

git checkout -b bug1

Ahora estás en la rama bug1. Realice algunos cambios, confirme, espere la revisión del código. Esto es local, por lo que no está afectando a nadie más, y debería ser bastante fácil hacer un parche de git diffs.

A-B < master
   \
    C < bug1

Ahora estás trabajando en bug2. Ir volver a principal con git checkout master. Hacer una nueva rama, git checkout -b bug2. Haga cambios, comprométase, espere la revisión del código.

    D < bug2
   /
A-B < master
   \
    C < bug1

Supongamos que otra persona comete E&F en master mientras espera la revisión.

    D < bug2
   /
A-B-E-F < master
   \
    C < bug1

Cuando se aprueba su código, puede volverlo a crear en master con los siguientes pasos:

git checkout bug1
git rebase master
git checkout master
git merge bug1

Esto resultará en lo siguiente:

    D < bug2
   /
A-B-E-F-C' < master, bug1

Luego puede presionar, eliminar su rama local bug1, y listo. Un error a la vez en su espacio de trabajo, pero con el uso de ramas locales, su repositorio puede manejar múltiples errores. Y esto evita una etapa complicada de baile.

Responda a la pregunta de ctote en los comentarios:

Bueno, puedes volver a esconder cada error y solo trabajar con un error a la vez. Al menos eso te ahorra el problema de la puesta en escena. Pero después de haber intentado esto, personalmente lo encuentro problemático. Los escondites son un poco desordenados en un gráfico de registro de git. Y lo más importante, si arruinas algo, no puedes revertirlo. Si tiene un directorio de trabajo sucio y hace estallar un alijo, no puede "deshacer" ese pop. Es mucho más difícil arruinar los compromisos ya existentes.

Por lo tanto git rebase -i.

Cuando vuelve a crear una rama en otra, puede hacerlo de forma interactiva (el indicador -i). Cuando haces esto, tienes la opción de elegir lo que quieres hacer con cada confirmación. Pro Git es un libro increíble que también está en línea en formato HTML, y tiene una buena sección sobre rebase y aplastamiento:

http://git-scm.com/book/ch6-4.html

Robaré su ejemplo literalmente por conveniencia. Suponga que tiene el siguiente historial de confirmaciones y desea volver a crear y aplastar bug1 en master:

    F < bug2
   /
A-B-G-H < master
   \
    C-D-E < bug1

Esto es lo que verá cuando escriba git rebase -i master bug1

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Para aplastar todos los commits de una rama en un solo commit, mantenga el primer commit como "pick" y reemplace todas las entradas posteriores "pick" con "squash" o simplemente "s". También tendrá la oportunidad de cambiar el mensaje de confirmación.

pick f7f3f6d changed my name a bit
s 310154e updated README formatting and added blame
s a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit

Así que sí, aplastar es un poco molesto, pero aún así lo recomendaría sobre el uso intensivo de los escondites.

Mike Monkiewicz
fuente
1
Gracias por la publicación detallada! Esto resuelve muchos de mis problemas con seguridad: el único problema que veo es que nuestro equipo actual ha solicitado que mantengamos todas las entregas en un solo compromiso. :(
MrDuk
1
Si no necesitan o quieren su historial de trabajo en el repositorio de producción, está bien: haga que su maestro de seguimiento no tenga historial mediante la aplicación de diferencias en lugar de fusionar ramas. Puede averiguar cómo mantener una rama maestra decorada que tenga el historial real combinado y hacer su trabajo real a partir de eso, de esa manera será fácil automatizar la generación de las diferencias correctas.
Jthill
2
Tenga en cuenta que git checkout master; git checkout -b bug2se puede acortar a git checkout -b bug2 master. Lo mismo se aplica a git checkout bug1; git rebase master; git checkout master; git merge bug1, que es idéntico a git rebase master bug1; git push . bug1:master(concedido, el pushtruco no es obvio)
knittl
1
Le di un tutorial para esconder arriba en la respuesta principal para poder usar un formato elegante
Mike Monkiewicz
66
He votado en contra porque esto no responde la pregunta original. Estoy en una rama trabajando en algo, y acabo de hacer un cambio que creo que debería comprometerse con la rama de integración por separado. Todo lo que quiero hacer es organizar ese cambio y guardarlo para poder cambiar a otra rama y comprometerme por separado, en lugar de mi rama actual de "trabajo en progreso". (Advertencia, git despotricando.) Es absurdo que esto sea tan difícil de hacer; Tengo que imaginar que esto es un hecho común . (Trabajando en una rama y detectando un cambio rápido que debe hacerse y olvidando cambiar primero.)
jpmc26
0

De sus comentarios a la respuesta de Mike Monkiewicz, sugiero usar un modelo más simple: use ramas de desarrollo regulares, pero use la opción de squash de la combinación para obtener una confirmación única en su rama maestra:

git checkout -b bug1    # create the development branch
* hack hack hack *      # do some work
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
git checkout master     # go back to the master branch
git merge --squash bug1 # merge the work back
git commit              # commit the merge (don't forget
                        #    to change the default commit message)
git branch -D bug1      # remove the development branch

La ventaja de este procedimiento es que puede usar el flujo de trabajo normal de git.

Rudi
fuente
No puedo ver cómo esta respuesta podría ayudar. No está relacionado con la pregunta original.
frapen
0

TL; DR ;git stash-staged

Después de crear un alias:

git config --global alias.stash-staged '!bash -c "git stash -- \$(git diff --staged --name-only)"'

Aquí git diffdevuelve la lista de --stagedarchivos --name-only
Y luego pasamos esta lista pathspeca git stashcommad.

De man git stash:

git stash [--] [<pathspec>...]

<pathspec>...
   The new stash entry records the modified states only for the files
   that match the pathspec. The index entries and working tree
   files are then rolled back to the state in HEAD only for these
   files, too, leaving files that do not match the pathspec intact.

Eugen Konkov
fuente
-1

Para podar un cambio accidental, especialmente la eliminación de varios archivos, haga lo siguiente:

git add <stuff to keep> && git stash --keep-index && git stash drop

en otras palabras, esconde la basura y tírala con el alijo por completo.

Probado en git versión 2.17.1

wmax
fuente
un voto negativo sin un comentario no es útil para mí ni para el próximo lector ... zaenks gruñón anon. Aunque puedo imaginar un problema con esta línea: uno debe tener mucho cuidado de no olvidar agregar todos los cambios deseados al índice, de lo contrario, esos cambios importantes también se eliminarán. Pero, de nuevo, el uso descuidado de cualquier herramienta cli puede ser muy peligroso para el precioso tiempo y el trabajo en el peor de los casos.
wmax