Cómo dividir el último commit en dos en Git

277

Tengo dos ramas de trabajo, principal y foro, y acabo de hacer algunas modificaciones en la rama del foro , que me gustaría elegir como maestro . Pero desafortunadamente, el commit que quiero seleccionar también contiene algunas modificaciones que no quiero.

La solución probablemente sería eliminar de alguna manera el commit incorrecto y reemplazarlo con dos commits separados, uno con los cambios que quiero elegir en master y otros que no pertenecen allí.

He intentado hacer

git reset --hard HEAD^

que eliminó todos los cambios, así que tuve que volver con

git reset ORIG_HEAD

Entonces mi pregunta es, ¿cuál es la mejor manera de dividir la última confirmación en dos confirmaciones separadas?

Jakub Arnold
fuente

Respuestas:

332

Deberías usar el índice. Después de hacer un reinicio mixto (" git reset HEAD ^"), agregue el primer conjunto de cambios en el índice, luego confírmelos. Luego comete el resto.

Puede usar " git add " para colocar todos los cambios realizados en un archivo en el índice. Si no desea organizar cada modificación realizada en un archivo, solo algunas, puede usar "git add -p".

Veamos un ejemplo. Supongamos que tengo un archivo llamado myfile, que contiene el siguiente texto:

something
something else
something again

Lo modifiqué en mi último commit para que ahora se vea así:

1
something
something else
something again
2

Ahora decido que quiero dividirlo en dos, y quiero que la inserción de la primera línea esté en la primera confirmación, y la inserción de la última línea en la segunda confirmación.

Primero vuelvo al padre de HEAD, pero quiero mantener las modificaciones en el sistema de archivos, así que uso "git reset" sin argumento (que hará un llamado reinicio "mixto"):

$ git reset HEAD^
myfile: locally modified
$ cat myfile
1
something
something else
something again
2

Ahora uso "git add -p" para agregar los cambios que quiero confirmar en el índice (= los pongo en escena). "git add -p" es una herramienta interactiva que le pregunta acerca de qué cambios al archivo debe agregar al índice.

$ git add -p myfile
diff --git a/myfile b/myfile
index 93db4cb..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,5 @@
+1
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,s,e,?]? s    # split this section into two!
Split into 2 hunks.
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again
Stage this hunk [y,n,a,d,/,j,J,g,e,?]? y  # yes, I want to stage this
@@ -1,3 +2,4 @@
 something
 something else
 something again
+2
Stage this hunk [y,n,a,d,/,K,g,e,?]? n   # no, I don't want to stage this

Luego cometo este primer cambio:

$ git commit -m "Added first line"
[master cef3d4e] Added first line
 1 files changed, 1 insertions(+), 0 deletions(-)

Ahora puedo confirmar todos los demás cambios (es decir, el número "2" puesto en la última línea):

$ git commit -am "Added last line"
[master 5e284e6] Added last line
 1 files changed, 1 insertions(+), 0 deletions(-)

Revisemos el registro para ver qué confirmaciones tenemos:

$ git log -p -n2 | cat
Commit 5e284e652f5e05a47ad8883d9f59ed9817be59d8
Author: ...
Date: ...

    Added last line

Diff --git a/myfile b/myfile
Index f9e1a67..2f113ce 100644
--- a/myfile
+++ b/myfile
@@ -2,3 +2,4 @@
 something
 something else
 something again
+2

Commit cef3d4e0298dd5d279a911440bb72d39410e7898
Author: ...
Date: ...

    Added first line

Diff --git a/myfile b/myfile
Index 93db4cb..f9e1a67 100644
--- a/myfile
+++ b/myfile
@@ -1,3 +1,4 @@
+1
 something
 something else
 something again
hcs42
fuente
1
Me he estado acostumbrando lentamente a git de Mercurial durante la última semana y media, y hay un comando de acceso directo útil git reset [--patch|-p] <commit>que puede usar para evitar la molestia de tener que hacerlo git add -pdespués de reiniciar. Estoy en lo cierto? Usando git 1.7.9.5.
trojjer
2
Aquí hay un poco más sobre esta técnica, incluido el rebasado si era una confirmación anterior, o si necesita cambiar las confirmaciones N a las confirmaciones M: emmanuelbernard.com/blog/2014/04/14/… .
Chris Westin
84

Metas:

  • Quiero dividir un commit anterior ( splitme) en dos.
  • Quiero mantener el mensaje de compromiso .

Plan:

  1. rebase interactivo de uno antes splitme.
  2. edición splitme.
  3. Restablezca los archivos para dividirlos en una segunda confirmación.
  4. Modifique el compromiso, mantenga el mensaje, modifique según sea necesario.
  5. Vuelva a agregar los archivos separados de la primera confirmación.
  6. Comprometerse con un nuevo mensaje.
  7. Continuar rebase.

Los pasos de rebase (1 y 7) se pueden omitir si splitmees el commit más reciente.

git rebase -i splitme^
# mark splitme commit with 'e'
git reset HEAD^ -- $files
git commit --amend
git add $files
git commit -m "commit with just some files"
git rebase --continue

Si quisiera que los archivos divididos se confirmaran primero, luego volvería a escribir -i nuevamente y cambiaría el orden

git rebase -i splitme^
# swap order of splitme and 'just some files'
Spazm
fuente
1
git reset HEAD^era la pieza faltante del rompecabezas. Funciona bien con -ptambién. ¡Gracias!
Marius Gedminas
10
Es importante tener en cuenta el -- $filesargumento a git reset. Con las rutas pasadas, git resetrestaura esos archivos al estado de la confirmación referenciada pero no cambia ninguna confirmación. Si deja los caminos, entonces "pierde" el compromiso que desea modificar en el siguiente paso.
marcadores de duelin
2
Este método evita que tenga que copiar y pegar su primer mensaje de confirmación nuevamente, en comparación con la respuesta aceptada.
Calvin
Además: si desea restablecer todos los archivos, simplemente use git reset HEAD^ -- .. Extremadamente sorprendente, este no es exactamente el comportamiento de git reset HEAD^.
allidoiswin
52

Para cambiar la confirmación actual en dos confirmaciones, puede hacer algo como lo siguiente.

Ya sea:

git reset --soft HEAD^

Esto deshace la última confirmación pero deja todo en escena. A continuación, puede desestabilizar ciertos archivos:

git reset -- file.file

Opcionalmente reajuste partes de esos archivos:

git add -p file.file

Haz un nuevo primer commit:

git commit

La etapa y cometer el resto de los cambios en una segunda confirmación:

git commit -a

O:

Deshacer y desestabilizar todos los cambios desde la última confirmación:

git reset HEAD^

Etapa selectiva de la primera ronda de cambios:

git add -p

Cometer:

git commit

Cometer el resto de los cambios:

git commit -a

(En cualquiera de los pasos, si deshizo una confirmación que agregó un archivo nuevo y desea agregarla a la segunda confirmación, tendrá que agregarla manualmente, ya que commit -asolo realiza cambios en los archivos ya rastreados).

CB Bailey
fuente
22

Ejecutar git gui, seleccione el botón de opción "Modificar la última confirmación" y desarme los cambios (Confirmar> Sin etapa de confirmación, o Ctrl- U) que no desea incluir en la primera confirmación. Creo que esa es la forma más fácil de hacerlo.

Otra cosa que podría hacer es seleccionar el cambio sin comprometer ( git cherry-pick -n) y luego manualmente o con git guiseleccionar los cambios deseados antes de confirmar.

Michael Krelin - hacker
fuente
15
git reset HEAD^

lo duro es lo que está matando tus cambios.

semanticart
fuente
13

Me sorprende que nadie sugirió git cherry-pick -n forum. Esto organizará los cambios desde la última forumconfirmación, pero no los confirmará; puede reseteliminar los cambios que no necesita y confirmar lo que desea conservar.

dahlbyk
fuente
3

El método de doble reversión de squash

  1. Realice otra confirmación que elimine los cambios no deseados. (Si es por archivo, esto es realmente fácil: git checkout HEAD~1 -- files with unwanted changesy git commit. Si no, los archivos con cambios mixtos pueden organizarse parcialmente git reset filey git add -p filecomo un paso intermedio). Llame a esto la reversión .
  2. git revert HEAD- Realice otra confirmación, que agrega los cambios no deseados. Esta es la doble reversión
  3. De las 2 confirmaciones que realizó ahora, aplaste la primera en la confirmación para dividir ( git rebase -i HEAD~3). Esta confirmación ahora se libera de los cambios no deseados, ya que están en la segunda confirmación.

Beneficios

  • Conserva el mensaje de confirmación
  • Funciona incluso si el commit para dividir no es el último. Solo requiere que los cambios no deseados no entren en conflicto con las confirmaciones posteriores
usuario2394284
fuente
1

Como estás recogiendo cerezas, puedes:

  1. cherry-pickcon la --no-commitopción agregada.
  2. resety usar add --patch, add --edito simplemente addpara poner en escena lo que quieres conservar.
  3. commit Los cambios escenificados.
    • Para reutilizar el mensaje de confirmación original, puede agregar --reuse-message=<old-commit-ref>u --reedit-message=<old-commit-ref>opciones al commitcomando.
  4. Sopla los cambios no organizados con reset --hard.

Otra forma, preservar o editar el mensaje de confirmación original:

  1. cherry-pick El commit original como de costumbre.
  2. Invierta los cambios que no desea y use addpara organizar la inversión.
    • Este paso sería fácil si elimina lo que agregó, pero un poco complicado si agrega lo que eliminó o revierte un cambio.
  3. commit --amend para efectuar la reversión en el compromiso seleccionado.
    • Obtendrá el mismo mensaje de confirmación nuevamente, que puede guardar o revisar según sea necesario.
ADTC
fuente
0

Esta podría ser otra solución destinada a casos en los que existe una gran confirmación y una pequeña cantidad de archivos debe trasladarse a una nueva confirmación. Esto funcionará si <path>se va a extraer un conjunto de archivos de la última confirmación en HEAD y todos se moverán a una nueva confirmación. Si se necesitan confirmaciones múltiples, se pueden usar las otras soluciones.

Primero haga parches en las áreas preparadas y no preparadas que contendrían los cambios para revertir el código antes de la modificación y después de la modificación respectivamente:

git reset HEAD^ <path>

$ git status
On branch <your-branch>
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   <path>

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   <path>

Para entender lo que sucederá (la flecha y los comentarios no son parte del comando):

git diff --cached   -> show staged changes to revert <path> to before HEAD
git diff            -> show unstaged changes to add current <path> changes

Revertir los <path>cambios en la última confirmación:

git commit --amend  -> reverts changes on HEAD by amending with staged changes

Crear nueva confirmación con <path>cambios:

git commit -a -m "New Commit" -> adds new commit with unstaged changes

Esto tiene el efecto de crear una nueva confirmación que contiene los cambios extraídos de la última confirmación.

Jose Cifuentes
fuente