Divide una confirmación anterior en varias confirmaciones

1225

Sin crear una rama y hacer un montón de trabajo funky en una nueva rama, ¿es posible dividir una sola confirmación en unas pocas confirmaciones diferentes después de que se haya confirmado en el repositorio local?

koblas
fuente
36
Una buena fuente para aprender a hacer esto es Pro Git §6.4 Git Tools - Rewriting History , en la sección "Splitting a Commit".
2
Los documentos vinculados en el comentario anterior son excelentes, mejor explicados que las respuestas a continuación.
Blaisorblade
2
Sugiero el uso de este alias stackoverflow.com/a/19267103/301717 . Permite dividir un commit usandogit autorebase split COMMIT_ID
Jérôme Pouiller
Lo más fácil de hacer sin un rebase interactivo es (probablemente) hacer una nueva rama que comience en la confirmación antes de la que desea dividir, seleccione el compromiso, restablezca, oculte, confirme el movimiento del archivo, vuelva a aplicar la ocultación y cometer los cambios, y luego fusionarse con la rama anterior o elegir los commits que siguieron. (Luego cambie el nombre de la sucursal anterior al encabezado actual). (Probablemente sea mejor seguir los consejos de MBO y hacer un rebase interactivo.) (Copiado de la respuesta de 2010 a continuación)
William Pursell
1
Me encontré con este problema después de que accidentalmente aplasté dos commits durante un rebase en un commit anterior. Mi forma de solucionarlo fue a la caja de la aplastado cometer git reset HEAD~, git stashy luego git cherry-pickcometer el primero dentro de la calabaza, a continuación git stash pop. Mi caso cereza-Pick es bastante específica aquí, pero git stash, y git stash popes muy útil para los demás.
SOFe

Respuestas:

1802

git rebase -i lo haré.

Primero, comience con un directorio de trabajo limpio: no git statusdebe mostrar modificaciones, eliminaciones o adiciones pendientes.

Ahora, debe decidir qué commit (s) desea dividir.

A) Dividir la confirmación más reciente

Para dividir su confirmación más reciente, primero:

$ git reset HEAD~

Ahora compromete las piezas individualmente de la forma habitual, produciendo tantos commits como necesites.

B) Dividir un commit más atrás

Esto requiere rebase , es decir, reescribir la historia. Para encontrar la confirmación correcta, tiene varias opciones:

  • Si fueron tres confirmaciones, entonces

    $ git rebase -i HEAD~3
    

    ¿Dónde 3está la cantidad de confirmaciones?

  • Si estaba más atrás en el árbol de lo que quieres contar, entonces

    $ git rebase -i 123abcd~
    

    ¿Dónde 123abcdestá el SHA1 de la confirmación que desea dividir?

  • Si está en una rama diferente (por ejemplo, una rama de características) que planea fusionar en maestra:

    $ git rebase -i master
    

Cuando obtenga la pantalla de edición de rebase, busque la confirmación que desea separar. Al comienzo de esa línea, reemplace pickcon edit( epara abreviar). Guarde el búfer y salga. Rebase ahora se detendrá justo después de la confirmación que desea editar. Entonces:

$ git reset HEAD~

Compromete las piezas individualmente de la manera habitual, produciendo tantos compromisos como necesites, luego

$ git rebase --continue
Wayne Conrad
fuente
2
Gracias por esta respuesta Quería tener algunos archivos previamente confirmados en el área de preparación, por lo que las instrucciones para mí fueron un poco diferentes. Antes de que pudiera git rebase --continue, de hecho tuve que git add (files to be added), git commity, a continuación git stash(para el resto de archivos). Después git rebase --continue, solía git checkout stash .obtener los archivos restantes
Eric Hu
18
La respuesta de manojlds en realidad tiene este enlace a la documentación en git-scm , que también explica el proceso de división de commits muy claramente.
56
También querrá aprovechar git add -ppara agregar solo secciones parciales de archivos, posiblemente con la eopción de editar diferencias para confirmar solo un trozo. git stashTambién es útil si desea llevar adelante algún trabajo pero eliminarlo de la confirmación actual.
Craig Ringer
2
Si desea dividir y reordenar confirmaciones, lo que me gusta hacer es dividir primero y luego reordenar por separado con otro git rebase -i HEAD^3comando. De esta manera, si la división va mal, no tiene que deshacer tanto trabajo.
David M. Lloyd
44
@kralyk Los archivos que se confirmaron recientemente en HEAD se dejarán en el disco después git reset HEAD~. No están perdidos
Wayne Conrad
312

Del manual de git-rebase (sección COMPROMISOS DE DIVISIÓN)

En el modo interactivo, puede marcar confirmaciones con la acción "editar". Sin embargo, esto no significa necesariamente que git rebase espere que el resultado de esta edición sea exactamente una confirmación. De hecho, puede deshacer la confirmación o puede agregar otras confirmaciones. Esto se puede usar para dividir un commit en dos:

  • Comience un rebase interactivo con git rebase -i <commit>^, donde <commit>está la confirmación que desea dividir. De hecho, cualquier rango de confirmación funcionará, siempre que contenga esa confirmación.

  • Marque la confirmación que desea dividir con la acción "editar".

  • Cuando se trata de editar esa confirmación, ejecutar git reset HEAD^. El efecto es que la CABEZA se rebobina por uno y el índice sigue su ejemplo. Sin embargo, el árbol de trabajo permanece igual.

  • Ahora agregue los cambios al índice que desea tener en la primera confirmación. Puede usar git add(posiblemente de manera interactiva) o git gui(o ambos) para hacer eso.

  • Confirme el índice actual con cualquier mensaje de confirmación que sea apropiado ahora.

  • Repita los dos últimos pasos hasta que su árbol de trabajo esté limpio.

  • Continúa el rebase con git rebase --continue.

MBO
fuente
12
En Windows tienes que usar en ~lugar de ^.
Kevin Kuszyk
13
Palabra de advertencia: con este enfoque, perdí el mensaje de confirmación.
user420667
11
@ user420667 Sí, por supuesto. resetDespués de todo, estamos haciendo el commit, mensaje incluido. Lo más prudente que debe hacer, si sabe que va a dividir una confirmación pero desea conservar parte o la totalidad de su mensaje, es tomar una copia de ese mensaje. Por lo tanto, git showconfirme antes rebase, o si olvida o prefiere esto: vuelva más tarde a través de reflog. Nada de eso se "perderá" hasta que se recoja la basura en 2 semanas o lo que sea.
underscore_d
44
~y ^son cosas diferentes, incluso en Windows. Todavía quieres el cursor ^, así que solo tendrás que escapar de él según sea apropiado para tu caparazón. En PowerShell, es HEAD`^. Con cmd.exe, puede duplicarlo para escapar como HEAD^^. En la mayoría (todos?) Conchas, se puede rodear con citas como "HEAD^".
AndrewF
77
También puedes hacer git commit --reuse-message=abcd123. La opción corta para ello es -C.
j0057
41

Use git rebase --interactivepara editar ese compromiso anterior, ejecutar git reset HEAD~y luego git add -pagregar algunos, luego hacer un compromiso, luego agregar un poco más y hacer otro compromiso, tantas veces como desee. Cuando haya terminado, ejecute git rebase --continue, y tendrá todas las confirmaciones divididas anteriormente en su pila.

Importante : tenga en cuenta que puede jugar y hacer todos los cambios que desee, y no tener que preocuparse por perder los cambios antiguos, porque siempre puede ejecutar git reflogpara encontrar el punto en su proyecto que contiene los cambios que desea, (llamémoslo a8c4ab) y luego git reset a8c4ab.

Aquí hay una serie de comandos para mostrar cómo funciona:

mkdir git-test; cd git-test; git init

ahora agrega un archivo A

vi A

agregue esta línea:

one

git commit -am one

luego agregue esta línea a A:

two

git commit -am two

luego agregue esta línea a A:

three

git commit -am three

ahora el archivo A se ve así:

one
two
three

y nuestro git logaspecto es el siguiente (bueno, yo usogit log --pretty=oneline --pretty="%h %cn %cr ---- %s"

bfb8e46 Rose Perrone 4 seconds ago ---- three
2b613bc Rose Perrone 14 seconds ago ---- two
9aac58f Rose Perrone 24 seconds ago ---- one

Digamos que queremos dividir la segunda cometió, two.

git rebase --interactive HEAD~2

Esto trae un mensaje que se ve así:

pick 2b613bc two
pick bfb8e46 three

Cambie el primero picka an epara editar esa confirmación.

git reset HEAD~

git diff nos muestra que acabamos de descifrar el commit que hicimos para el segundo commit:

diff --git a/A b/A
index 5626abf..814f4a4 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two

Pongamos en escena ese cambio y agreguemos "y un tercero" a esa línea en el archivo A.

git add .

Este suele ser el punto durante un rebase interactivo en el que correríamos git rebase --continue, porque generalmente solo queremos volver a nuestra pila de confirmaciones para editar una confirmación anterior. Pero esta vez, queremos crear una nueva confirmación. Entonces correremos git commit -am 'two and a third'. Ahora editamos el archivo Ay agregamos la línea two and two thirds.

git add . git commit -am 'two and two thirds' git rebase --continue

Tenemos un conflicto con nuestro compromiso three, así que resolvámoslo:

Vamos a cambiar

one
<<<<<<< HEAD
two and a third
two and two thirds
=======
two
three
>>>>>>> bfb8e46... three

a

one
two and a third
two and two thirds
three

git add .; git rebase --continue

Ahora nuestro git log -paspecto es el siguiente:

commit e59ca35bae8360439823d66d459238779e5b4892
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 13:57:00 2013 -0700

    three

diff --git a/A b/A
index 5aef867..dd8fb63 100644
--- a/A
+++ b/A
@@ -1,3 +1,4 @@
 one
 two and a third
 two and two thirds
+three

commit 4a283ba9bf83ef664541b467acdd0bb4d770ab8e
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 14:07:07 2013 -0700

    two and two thirds

diff --git a/A b/A
index 575010a..5aef867 100644
--- a/A
+++ b/A
@@ -1,2 +1,3 @@
 one
 two and a third
+two and two thirds

commit 704d323ca1bc7c45ed8b1714d924adcdc83dfa44
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 14:06:40 2013 -0700

    two and a third

diff --git a/A b/A
index 5626abf..575010a 100644
--- a/A
+++ b/A
@@ -1 +1,2 @@
 one
+two and a third

commit 9aac58f3893488ec643fecab3c85f5a2f481586f
Author: Rose Perrone <[email protected]>
Date:   Sun Jul 7 13:56:40 2013 -0700

    one

diff --git a/A b/A
new file mode 100644
index 0000000..5626abf
--- /dev/null
+++ b/A
@@ -0,0 +1 @@
+one
Rose Perrone
fuente
39

Las respuestas anteriores han cubierto el uso de git rebase -ipara editar la confirmación que desea dividir y confirmarla en partes.

Esto funciona bien al dividir los archivos en diferentes confirmaciones, pero si desea separar los cambios en los archivos individuales, hay más que necesita saber.

Después de llegar al compromiso que desea dividir, usarlo rebase -iy marcarlo edit, tiene dos opciones.

  1. Después de usar git reset HEAD~, revise los parches individualmente git add -ppara seleccionar los que desea en cada confirmación

  2. Edite la copia de trabajo para eliminar los cambios que no desea; cometer ese estado provisional; y luego retira el commit completo para la siguiente ronda.

La opción 2 es útil si está dividiendo una confirmación grande, ya que le permite comprobar que las versiones provisionales se compilan y ejecutan correctamente como parte de la fusión. Esto procede de la siguiente manera.

Después de usar rebase -ie editing el commit, use

git reset --soft HEAD~

para deshacer la confirmación, pero deje los archivos confirmados en el índice. También puede hacer un restablecimiento mixto omitiendo --soft, dependiendo de qué tan cerca del resultado final estará su confirmación inicial. La única diferencia es si comienzas con todos los cambios organizados o con todos sin clasificar.

Ahora entra y edita el código. Puede eliminar cambios, eliminar archivos agregados y hacer lo que quiera para construir la primera confirmación de la serie que está buscando. También puede compilarlo, ejecutarlo y confirmar que tiene un conjunto de fuentes coherente.

Una vez que esté satisfecho, ponga en escena / elimine los archivos según sea necesario (me gusta usar git guipara esto) y confirme los cambios a través de la interfaz de usuario o la línea de comandos

git commit

Ese es el primer compromiso realizado. Ahora desea restaurar su copia de trabajo al estado que tenía después de la confirmación que está dividiendo, para que pueda tomar más de los cambios para su próxima confirmación. Para encontrar el sha1 del commit que está editando, use git status. En las primeras líneas del estado verá el comando rebase que se está ejecutando actualmente, en el que puede encontrar el sha1 de su commit original:

$ git status
interactive rebase in progress; onto be83b41
Last commands done (3 commands done):
   pick 4847406 US135756: add debugging to the file download code
   e 65dfb6a US135756: write data and download from remote
  (see more in file .git/rebase-merge/done)
...

En este caso, el commit que estoy editando tiene sha1 65dfb6a. Sabiendo eso, puedo consultar el contenido de esa confirmación en mi directorio de trabajo utilizando la forma de la git checkoutcual se toma una confirmación y una ubicación de archivo. Aquí lo uso .como ubicación del archivo para reemplazar toda la copia de trabajo:

git checkout 65dfb6a .

¡No te pierdas el punto al final!

Esto verificará y organizará los archivos tal como estaban después de la confirmación que está editando, pero en relación con la confirmación anterior que realizó, por lo que cualquier cambio que ya haya confirmado no formará parte de la confirmación.

Puede continuar ahora y confirmarlo tal como está para terminar la división, o volver a hacerlo, eliminando algunas partes de la confirmación antes de realizar otra confirmación provisional.

Si desea reutilizar el mensaje de confirmación original para una o más confirmaciones, puede usarlo directamente desde los archivos de trabajo de la nueva versión:

git commit --file .git/rebase-merge/message

Finalmente, una vez que haya comprometido todos los cambios,

git rebase --continue

continuará y completará la operación de rebase.

Andy Mortimer
fuente
3
¡¡¡Gracias!!! Esta debería ser la respuesta aceptada. Me habría ahorrado mucho tiempo y dolor hoy. Es la única respuesta donde el resultado de la confirmación final lo lleva al mismo estado que la confirmación en edición.
Doug Coburn
1
Me gusta cómo usas el mensaje de confirmación original.
Salamandar
Usando la opción 2, cuando lo hago git checkout *Sha I'm Editing* .siempre dice Updated 0 paths from *Some Sha That's Not In Git Log*y no da cambios.
Noumenon
18

git rebase --interactivese puede usar para dividir una confirmación en confirmaciones más pequeñas. Los documentos de Git sobre rebase tienen un recorrido conciso del proceso - Compromisos de división :

En el modo interactivo, puede marcar confirmaciones con la acción "editar". Sin embargo, esto no significa necesariamente que git rebaseespera que el resultado de esta edición sea exactamente una confirmación. De hecho, puede deshacer la confirmación o puede agregar otras confirmaciones. Esto se puede usar para dividir un commit en dos:

  • Comience un rebase interactivo con git rebase -i <commit>^, donde <commit>está la confirmación que desea dividir. De hecho, cualquier rango de confirmación funcionará, siempre que contenga esa confirmación.

  • Marque la confirmación que desea dividir con la acción "editar".

  • Cuando se trata de editar esa confirmación, ejecutar git reset HEAD^. El efecto es que la CABEZA se rebobina por uno y el índice sigue su ejemplo. Sin embargo, el árbol de trabajo permanece igual.

  • Ahora agregue los cambios al índice que desea tener en la primera confirmación. Puede usar git add(posiblemente de forma interactiva) o git gui (o ambos) para hacer eso.

  • Confirme el índice actual con cualquier mensaje de confirmación que sea apropiado ahora.

  • Repita los dos últimos pasos hasta que su árbol de trabajo esté limpio.

  • Continúa el rebase con git rebase --continue.

Si no está absolutamente seguro de que las revisiones intermedias son consistentes (se compilan, pasan la prueba, etc.), debe usar git stashpara guardar los cambios aún no confirmados después de cada confirmación, prueba y enmendar la confirmación si es necesario hacer arreglos. .


fuente
En Windows, recuerde que ^es un carácter de escape para la línea de comandos: debe duplicarse. Por ejemplo, emita en git reset HEAD^^lugar de git reset HEAD^.
Frédéric
@ Frédéric: s Nunca me he encontrado con esto. Al menos en PowerShell, este no es el caso. Luego, al usar ^dos veces, se restablecen dos confirmaciones por encima del HEAD actual.
Farway
@ Lejos, pruébelo en una línea de comando clásica. PowerShell es otra bestia, su carácter de escape es el backtilt.
Frédéric
Para resumir: "HEAD^"en cmd.exe o PowerShell, HEAD^^en cmd.exe, HEAD`^en PowerShell. Es útil aprender sobre cómo funcionan los shells y su shell particular (es decir, cómo un comando se convierte en partes individuales que se pasan al programa) para que pueda adaptar los comandos en línea a los caracteres correctos para su shell particular. (No específico para Windows.)
AndrewF
11

Ahora, en la última versión de TortoiseGit en Windows, puede hacerlo muy fácilmente.

Abra el cuadro de diálogo de rebase, configúrelo y siga los siguientes pasos.

  • Haga clic con el botón derecho en el commit que desea dividir y seleccione " Edit" (entre pick, squash, delete ...).
  • Haga clic en " Start" para comenzar a rebasar.
  • Una vez que llegue al commit para dividir, marque el Edit/Splitbotón " " y haga clic en " Amend" directamente. Se abre el diálogo de confirmación.
    Editar / dividir confirmación
  • Anule la selección de los archivos que desea poner en una confirmación por separado.
  • Edite el mensaje de confirmación y luego haga clic en " commit".
  • Hasta que haya archivos para confirmar, el diálogo de confirmación se abrirá una y otra vez. Cuando no haya más archivos para confirmar, aún le preguntará si desea agregar una confirmación más.

Muy útil, gracias TortoiseGit!

Mikaël Mayer
fuente
10

Puedes hacer rebase interactivo git rebase -i. La página del manual tiene exactamente lo que quieres:

http://git-scm.com/docs/git-rebase#_splitting_commits

manojlds
fuente
14
Dar un poco más de contexto sobre cómo abordar los problemas en lugar de dar un RTFM sería un poco más útil.
Jordan Dea-Mattson
8

Tenga en cuenta que también hay git reset --soft HEAD^. Es similar a git reset(que está predeterminado --mixed) pero conserva el contenido del índice. De modo que si ha agregado / eliminado archivos, ya los tiene en el índice.

Resulta ser muy útil en caso de confirmaciones gigantes.

letal
fuente
3

Aquí se explica cómo dividir una confirmación en IntelliJ IDEA , PyCharm , PhpStorm , etc.

  1. En la ventana de registro de Control de versiones, seleccione la confirmación que desea dividir, haga clic derecho y seleccione el Interactively Rebase from Here

  2. marque el que desea dividir como edit, haga clic enStart Rebasing

  3. Debería ver que se coloca una etiqueta amarilla, lo que significa que HEAD está configurado para esa confirmación. Haga clic derecho en esa confirmación, seleccioneUndo Commit

  4. Ahora esas confirmaciones están de vuelta en el área de preparación, luego puede confirmarlas por separado. Después de que se hayan comprometido todos los cambios, el antiguo compromiso queda inactivo.

bzuo
fuente
2

Lo más fácil de hacer sin un rebase interactivo es (probablemente) hacer una nueva rama que comience en la confirmación antes de la que desea dividir, seleccione el compromiso, restablezca, oculte, confirme el movimiento del archivo, vuelva a aplicar la ocultación y cometer los cambios, y luego fusionarse con la rama anterior o elegir los commits que siguieron. (Luego cambie el nombre de la sucursal anterior al encabezado actual). (Probablemente sea mejor seguir los consejos de MBO y hacer un rebase interactivo).

William Pursell
fuente
de acuerdo con los estándares de SO en estos días, esto debería calificarse como no una respuesta; pero esto aún puede ser útil para otros, así que si no te importa, pasa esto a los comentarios de la publicación original
YakovL
@YakovL parece razonable. Sobre el principio de acción mínima, no eliminaré la respuesta, pero no me opondría si alguien más lo hace.
William Pursell
Esto sería mucho más fácil que todas las rebase -isugerencias. Sin embargo, creo que esto no recibió mucha atención debido a la falta de formato. Tal vez podría revisarlo, ahora que tiene 126k puntos y probablemente sepa SO. ;)
erikbwork
1

Si tienes esto:

A - B <- mybranch

Donde ha comprometido algún contenido en commit B:

/modules/a/file1
/modules/a/file2
/modules/b/file3
/modules/b/file4

Pero desea dividir B en C - D y obtener este resultado:

A - C - D <-mybranch

Puede dividir el contenido de esta manera, por ejemplo (contenido de diferentes directorios en diferentes confirmaciones) ...

Restablezca la rama de nuevo a la confirmación antes de la división:

git checkout mybranch
git reset --hard A

Crear primer commit (C):

git checkout B /modules/a
git add -u
git commit -m "content of /modules/a"

Crear segunda confirmación (D):

git checkout B /modules/b
git add -u
git commit -m "content of /modules/b"
Martin G
fuente
¿Qué pasa si hay confirmaciones por encima de B?
CoolMind
1

Han pasado más de 8 años, pero tal vez alguien lo encuentre útil de todos modos. Pude hacer el truco sin él rebase -i. La idea es llevar a git al mismo estado que tenía antes git commit:

# first rewind back (mind the dot,
# though it can be any valid path,
# for instance if you want to apply only a subset of the commit)
git reset --hard <previous-commit> .

# apply the changes
git checkout <commit-you-want-to-split>

# we're almost there, but the changes are in the index at the moment,
# hence one more step (exactly as git gently suggests):
# (use "git reset HEAD <file>..." to unstage)
git reset

Después de esto, verá esto brillante Unstaged changes after reset:y su repositorio está en un estado como si estuviera a punto de confirmar todos estos archivos. A partir de ahora, puede volver a cometerlo fácilmente, como suele hacer. Espero eso ayude.

Stanislav E. Govorov
fuente
0

Una referencia rápida de los comandos necesarios, porque básicamente sé qué hacer pero siempre olvido la sintaxis correcta:

git rebase -i <sha1_before_split>
# mark the targeted commit with 'edit'
git reset HEAD^
git add ...
git commit -m "First part"
git add ...
git commit -m "Second part"
git rebase --continue

Créditos a la publicación del blog de Emmanuel Bernard .

Sparkofska
fuente