Usos prácticos de git reset --soft?

136

He estado trabajando con git por poco más de un mes. De hecho, ayer utilicé el reinicio por primera vez, pero el reinicio por software todavía no tiene mucho sentido para mí.

Entiendo que puedo usar el restablecimiento parcial para editar una confirmación sin alterar el índice o el directorio de trabajo, como lo haría con git commit --amend.

¿Son estos dos comandos realmente iguales ( reset --softvs commit --amend)? ¿Alguna razón para usar uno u otro en términos prácticos? Y lo que es más importante, ¿hay otros usos reset --softademás de enmendar un commit?

AJJ
fuente

Respuestas:

110

git resetse trata de mudarse HEAD, y generalmente la rama ref .
Pregunta: ¿qué pasa con el árbol de trabajo y el índice?
Cuando se emplea con --soft, movimientos HEAD, más a menudo posible actualizar el árbitro rama, y sólo elHEAD .
Esto difiere de commit --amendcomo:

  • no crea una nueva confirmación.
  • en realidad puede mover HEAD a cualquier confirmación (ya commit --amendque solo se trata de no mover HEAD, mientras permite rehacer la confirmación actual)

Acabo de encontrar este ejemplo de combinación:

  • una fusión clásica
  • una fusión de subárbol

todo en uno (pulpo, ya que hay más de dos ramas fusionadas) cometer fusión.

Tomas "wereHamster" Carnecky explica en su artículo "Subtree Octopus merge" :

  • La estrategia de fusión de subárbol se puede usar si desea fusionar un proyecto en un subdirectorio de otro proyecto y, posteriormente, mantener el subproyecto actualizado. Es una alternativa a los submódulos git.
  • La estrategia de combinación de pulpo se puede utilizar para combinar tres o más ramas. La estrategia normal puede fusionar solo dos ramas y si intentas fusionar más que eso, git vuelve automáticamente a la estrategia de pulpo.

El problema es que solo puedes elegir una estrategia. Pero quería combinar los dos para obtener un historial limpio en el que todo el repositorio se actualiza atómicamente a una nueva versión.

Tengo un superproyecto, llamémoslo projectA, y un subproyecto projectB, que fusioné en un subdirectorio projectA.

(esa es la parte de combinación de subárbol)

También mantengo algunos compromisos locales.
ProjectAse actualiza regularmente, projectBtiene una nueva versión cada dos días o semanas y generalmente depende de una versión particular de projectA.

Cuando decido actualizar ambos proyectos, no me limito a retirarlos projectAy projectB eso crearía dos confirmaciones para lo que debería ser una actualización atómica de todo el proyecto .
En cambio, creo una única confirmación de combinación que combina projectA, projectBy mis confirmaciones locales .
La parte difícil aquí es que se trata de una fusión de pulpo (tres cabezas), pero projectBdebe fusionarse con la estrategia de subárbol . Entonces esto es lo que hago:

# Merge projectA with the default strategy:
git merge projectA/master

# Merge projectB with the subtree strategy:
git merge -s subtree projectB/master

Aquí el autor usó un reset --hard, y luego read-treepara restaurar lo que las dos primeras fusiones habían hecho en el árbol de trabajo y el índice, pero ahí es donde reset --softpuede ayudar:
Cómo rehacer esas dos fusiones , que han funcionado, es decir, mi árbol de trabajo e índice son bien, pero sin tener que grabar esos dos commits?

# Move the HEAD, and just the HEAD, two commits back!
git reset --soft HEAD@{2}

Ahora, podemos reanudar la solución de Tomás:

# Pretend that we just did an octopus merge with three heads:
echo $(git rev-parse projectA/master) > .git/MERGE_HEAD
echo $(git rev-parse projectB/master) >> .git/MERGE_HEAD

# And finally do the commit:
git commit

Entonces, cada vez:

  • está satisfecho con lo que termina (en términos de árbol de trabajo e índice)
  • que está no satisfecho con todas las confirmaciones que se ha tomado en llegar:

git reset --soft es la respuesta.

VonC
fuente
8
Nota personal: ejemplo simple de aplastar con git reset --soft: stackoverflow.com/questions/6869705/…
VonC
3
También es útil si te comprometiste con la rama equivocada. todos los cambios vuelven al área de preparación y se mueven con usted mientras revisa la rama derecha.
smoebody
44

Caso de uso: combine una serie de confirmaciones locales

"Oops. Esos tres commits podrían ser solo uno".

Entonces, deshaga los últimos 3 (o lo que sea) confirmaciones (sin afectar el índice ni el directorio de trabajo). Luego, confirme todos los cambios como uno solo.

P.ej

> git add -A; git commit -m "Start here."
> git add -A; git commit -m "One"
> git add -A; git commit -m "Two"
> git add -A' git commit -m "Three"
> git log --oneline --graph -4 --decorate

> * da883dc (HEAD, master) Three
> * 92d3eb7 Two
> * c6e82d3 One
> * e1e8042 Start here.

> git reset --soft HEAD~3
> git log --oneline --graph -1 --decorate

> * e1e8042 Start here.

Ahora todos sus cambios se conservan y están listos para ser confirmados como uno solo.

Respuestas cortas a sus preguntas.

¿Son estos dos comandos realmente iguales ( reset --softvs commit --amend)?

  • No.

¿Alguna razón para usar uno u otro en términos prácticos?

  • commit --amend para agregar archivos / rm desde la última confirmación o para cambiar su mensaje.
  • reset --soft <commit> combinar varias confirmaciones secuenciales en una nueva.

Y lo que es más importante, ¿hay otros usos reset --softademás de enmendar un commit?

  • Ver otras respuestas :)
Shaun Luttin
fuente
2
Vea otras respuestas para "¿hay otros usos reset --softademás de enmendar un commit - No"
Antony Hatchkins
En respuesta al último comentario, estuvo de acuerdo en parte, pero diría que enmendar un solo compromiso es demasiado específico. Por ejemplo, es posible que desee, una vez que se escribe una característica, aplastar todo y crear nuevas confirmaciones que sean más útiles para los revisores. Diría que los reinicios suaves (incluidos los simples git reset) son buenos cuando (a) desea reescribir el historial, (b) no le importan las confirmaciones antiguas (para que pueda evitar la irritabilidad de la rebase interactiva) y (c) tener más de un compromiso de cambio (de lo contrario, commit --amendes más simple).
johncip
18

Lo uso para enmendar más que solo el último commit.

Digamos que cometí un error en el compromiso A y luego cometí el compromiso B. Ahora solo puedo enmendar B. Entonces lo hago git reset --soft HEAD^^, corrijo y vuelvo a confirmar A y luego vuelvo a confirmar B.

Por supuesto, no es muy conveniente para grandes confirmaciones ... pero no deberías realizar grandes confirmaciones de todos modos ;-)

Simón
fuente
3
git commit --fixup HEAD^^ git rebase --autosquash HEAD~XFunciona bien también.
Niklas
3
git rebase --interactive HEAD^^donde elige editar las confirmaciones A y B. De esta manera, se mantienen los mensajes de confirmación de A y B; si es necesario, también puede modificarlos.
Paul Pladijs
2
¿Cómo vuelvo a confirmar B después de restablecer A?
northben
1
Otra forma en que es probablemente menos obra, compruebe el registro de git, obtener el hash de B confirma, entonces git reset A, la marca y añadir cambios, git commit --amend, git cherry-pick <B-commit-hash>.
naught101
15

Otro uso potencial es como una alternativa al alijo (que a algunas personas no les gusta, ver, por ejemplo, https://codingkilledthecat.wordpress.com/2012/04/27/git-stash-pop-considered-harmful/ ).

Por ejemplo, si estoy trabajando en una sucursal y necesito arreglar algo urgentemente en master, puedo hacer lo siguiente:

git commit -am "In progress."

luego finalice la compra y haga la corrección. Cuando termine, regreso a mi sucursal y hago

git reset --soft HEAD~1

para seguir trabajando donde lo dejé.

deltacrux
fuente
3
Actualización (ahora que entiendo mejor git): en --softrealidad es innecesario aquí, a menos que realmente te importe que los cambios se realicen de inmediato. Ahora solo uso git reset HEAD~cuando hago esto. Si tengo algunos cambios por etapas cuando necesito para cambiar ramas, y quiero que siga siendo así, entonces lo hago git commit -m "staged changes", entonces git commit -am "unstaged changes", más tarde git reset HEAD~seguido de git reset --soft HEAD~restaurar completamente el estado de trabajo. Aunque, para ser sincero, hago ambas cosas mucho menos ahora que sé sobre git-worktree:)
deltacrux
7

Puede usar git reset --softpara cambiar la versión que desea tener como padre para los cambios que tiene en su índice y árbol de trabajo. Los casos en que esto es útil son raros. A veces puede decidir que los cambios que tiene en su árbol de trabajo deberían pertenecer a una rama diferente. O puede usar esto como una forma simple de contraer varias confirmaciones en una (similar a squash / fold).

Vea esta respuesta de VonC para un ejemplo práctico: ¿ Aplastar los dos primeros commits en Git?

Johannes Rudolph
fuente
Esa es una buena pregunta que no había encontrado antes. Pero no estoy seguro de poder ver cómo colocar cambios en otra rama usando reset soft. Entonces tengo Checkout Branch, luego uso git reset --soft anotherBranchy luego me comprometo allí. Pero en realidad no cambias la rama de ckeckout, entonces ¿te comprometerás con Branch u otra rama ?
AJJ
Al hacer esto, es importante que no uses git checkout porque eso alterará tu árbol. Piense en git reset --soft como una forma de manipular los puntos de revisión HEAD.
Johannes Rudolph
7

Un posible uso sería cuando desea continuar su trabajo en una máquina diferente. Funcionaría así:

  1. Verifique una nueva sucursal con un nombre similar a un alijo,

    git checkout -b <branchname>_stash
    
  2. Empuja tu rama escondida hacia arriba,

    git push -u origin <branchname>_stash
    
  3. Cambie a su otra máquina.

  4. Tire hacia abajo tanto su alijo como las ramas existentes,

    git checkout <branchname>_stash; git checkout <branchname>
    
  5. Debería estar en su sucursal actual ahora. Combinar en los cambios de la rama de alijo,

    git merge <branchname>_stash
    
  6. Reinicie su rama existente a 1 antes de su fusión,

    git reset --soft HEAD^
    
  7. Retira tu rama escondida,

    git branch -d <branchname>_stash
    
  8. También quite su rama oculta del origen,

    git push origin :<branchname>_stash
    
  9. Continúe trabajando con sus cambios como si los hubiera escondido normalmente.

Creo que, en el futuro, GitHub y compañía. debería ofrecer esta funcionalidad de "escondite remoto" en menos pasos.

llaughlin
fuente
2
Quiero señalar que el primer alijo y pop en su primera máquina son completamente innecesarios, simplemente puede crear una nueva rama directamente desde una copia de trabajo sucia, confirmar y luego empujar los cambios al control remoto.
7

Un uso práctico es si ya se ha comprometido con su repositorio local (es decir, git commit -m), entonces puede revertir esa última confirmación haciendo git reset --soft HEAD ~ 1

También para su conocimiento, si ya ha organizado sus cambios (es decir, con git add.), Entonces puede revertir la puesta en escena haciendo git reset - HEAD mixto o también he usado comúnmentegit reset

Por último, git reset --hard borra todo, incluidos los cambios locales. El ~ después de la cabeza le dice a cuántos compromisos ir desde la parte superior.

j2emanue
fuente
1
git reset --soft HEAD ~1me da fatal: Cannot do soft reset with paths.creo que necesitamos eliminar el espacio después de HEAD para que así seagit reset --soft HEAD~1
Andrew Lohr
6

Una buena razón para usar ' git reset --soft <sha1>' es moverse HEADen un repositorio desnudo.

Si intenta usar la opción --mixedo --hard, obtendrá un error ya que está intentando modificar y trabajar un árbol y / o índice que no existe.

Nota: Deberá hacer esto directamente desde el repositorio simple.

Nota de nuevo: deberá asegurarse de que la rama que desea restablecer en el repositorio simple sea la rama activa. De lo contrario, siga la respuesta de VonC sobre cómo actualizar la rama activa en un repositorio simple cuando tenga acceso directo al repositorio.

Hazok
fuente
1
Como se mencionó en su respuesta anterior ( stackoverflow.com/questions/4624881/… ), esto es cierto cuando tiene acceso directo al repositorio simple (que menciona aquí). +1 sin embargo.
VonC
@VonC Sí, absolutamente correcto y gracias por agregar la nota. Me olvido de agregar eso, ya que supongo que los reinicios se realizan directamente desde el repositorio. Además, supongo que la rama que la persona quiere restablecer en el repositorio simple es su rama activa. Si la rama no es su rama activa, entonces la necesidad de actualizar según su respuesta ( stackoverflow.com/questions/3301956/… ) sobre cómo actualizar la rama activa para repositorios desnudos. Actualizaré la respuesta con la información de la rama activa también. ¡¡¡Gracias de nuevo!!!
Hazok
2

SourceTree es una GUI de git que tiene una interfaz bastante conveniente para organizar solo los bits que desea. No tiene nada remotamente similar para modificar una revisión adecuada.

Por git reset --soft HEAD~1lo tanto, es mucho más útil que commit --amenden este escenario. Puedo deshacer la confirmación, recuperar todos los cambios en el área de preparación y continuar ajustando los bits en escena usando SourceTree.

Realmente, me parece que commit --amendes el comando más redundante de los dos, pero git es git y no rehuye comandos similares que hacen cosas ligeramente diferentes.

Roman Starkov
fuente
1

Si bien me gustan las respuestas en este hilo, lo uso git reset --softpara un escenario ligeramente diferente, pero muy práctico, sin embargo.

Utilizo un IDE para el desarrollo que tiene una buena herramienta de diferencias para mostrar los cambios (en etapas y sin etapas) después de mi última confirmación. Ahora, la mayoría de mis tareas implican múltiples confirmaciones. Por ejemplo, digamos que hago 5 confirmaciones para completar una tarea en particular. Utilizo la herramienta diff en el IDE durante cada confirmación incremental de 1-5 para ver mis cambios desde la última confirmación. Me parece una forma muy útil de revisar mis cambios antes de comprometerme.

Pero al final de mi tarea, cuando quiero ver todos mis cambios juntos (desde antes del primer commit), para hacer una auto revisión de código antes de hacer una solicitud de extracción, solo vería los cambios de mi commit anterior (después del commit 4) y no cambios de todos los commits de mi tarea actual.

Así que git reset --soft HEAD~4solía retroceder 4 commits. Esto me permite ver todos los cambios juntos. Cuando estoy seguro de mis cambios, puedo hacerlo git reset HEAD@{1}y llevarlo a control remoto con confianza.

Codificador elegante
fuente
1
... luego hazlo git add --patchy git commitrepetidamente para construir la serie de commit que hubieras construido si supieras lo que estabas haciendo todo el tiempo. Sus primeras confirmaciones son como las notas en su escritorio o el primer borrador de un memo, están ahí para organizar su pensamiento, no para su publicación.
jthill
Oye, no entendí lo que estás sugiriendo.
Swanky Coder
1
Solo que en lugar de git reset @{1}restaurar su primer borrador de serie, puede crear una serie para publicación a partir de git add -py git commit.
jthill
Sí correcto. Otra forma es (la que suelo seguir) para hacer un rebase en upstream / master y aplastar los commits en uno solo.
Swanky Coder
1

Otro caso de uso es cuando desea reemplazar la otra rama con la suya en una solicitud de extracción, por ejemplo, supongamos que tiene un software con las características A, B, C en desarrollo.

Está desarrollando con la próxima versión y usted:

  • Función eliminada B

  • Característica agregada D

En el proceso, desarrolle las revisiones recién agregadas para la característica B.

Puede combinar el desarrollo en el siguiente, pero eso puede ser complicado a veces, pero también puede usar git reset --soft origin/developy crear una confirmación con sus cambios y la rama se puede fusionar sin conflictos y mantener sus cambios.

Resulta que git reset --softes un comando útil. Personalmente lo uso mucho para aplastar los compromisos que no tienen "trabajo completado" como "WIP", por lo que cuando abro la solicitud de extracción, todos mis compromisos son comprensibles.

cnexans
fuente