¿Cómo puedo dividir un commit de Git enterrado en la historia?

292

Cambié mi historial y quiero hacer algunos cambios. El problema es que tengo un commit con dos cambios no relacionados, y este commit está rodeado por otros cambios en mi historial local (no push).

Quiero dividir este commit antes de sacarlo, pero la mayoría de las guías que estoy viendo tienen que ver con dividir su commit más reciente o cambios locales no confirmados. ¿Es factible hacer esto a un commit que está enterrado en la historia un poco, sin tener que "rehacer" mis commits desde entonces?

Ben
fuente

Respuestas:

450

Hay una guía para dividir commits en la página de manual de rebase . El resumen rápido es:

  • Realice un rebase interactivo que incluya el commit de destino (por ejemplo git rebase -i <commit-to-split>^ branch) y márquelo para editarlo.

  • Cuando el rebase alcanza ese compromiso, úselo git reset HEAD^para restablecerlo antes del compromiso, pero mantenga su árbol de trabajo intacto.

  • Agregue los cambios de forma incremental y comprométalos, haciendo tantos commits como desee. add -ppuede ser útil agregar solo algunos de los cambios en un archivo determinado. Úselo commit -c ORIG_HEADsi desea reutilizar el mensaje de confirmación original para una confirmación determinada.

  • Si desea probar lo que está comprometiendo (¡buena idea!) Use git stashpara ocultar la parte que no ha comprometido (o stash --keep-indexincluso antes de comprometerla), pruebe y luego git stash popdevuelva el resto al árbol de trabajo. Siga haciendo confirmaciones hasta que se confirmen todas las modificaciones, es decir, tenga un árbol de trabajo limpio.

  • Ejecute git rebase --continuepara continuar aplicando los commits después del commit ahora dividido.

Cascabel
fuente
17
... pero nada de eso si ya empujaste el historial desde que se comprometió a dividirse.
wilhelmtell
29
@wilhelmtell: omití mi habitual "potencialmente peligroso; ver 'recuperación de rebase upstream'" porque el OP dijo explícitamente que no había empujado esta historia.
Cascabel
2
e hiciste una lectura perfecta. Intenté evitar el 'repetitivo' cuando especifiqué que aún no se compartía el historial :) En cualquier aspecto, he tenido éxito con su sugerencia. Sin embargo, es un gran dolor hacer esto después del hecho. ¡Aprendí una lección aquí, y eso es para asegurarme de que los commits se introducen correctamente para empezar!
Ben
2
El primer paso puede expresarse mejor como git rebase -i <sha1_of_the_commit_to_split>^ branch. Y git guies una buena herramienta para la tarea de división, que se puede usar para agregar diferentes partes de un archivo en diferentes confirmaciones.
Qiang Xu
3
@QiangXu: La primera es una sugerencia razonable. La segunda es exactamente la razón por la que sugerí git add -p, que puede hacer más de lo que git guipuede en este departamento (en particular, editar trozos, organizar todo a partir del trozo actual y buscar trozos por expresiones regulares).
Cascabel
3

Aquí se explica cómo hacerlo con Magit .

Digamos que commit ed417ae es el que quieres cambiar; contiene dos cambios no relacionados y está enterrado bajo una o más confirmaciones. Presione llpara mostrar el registro y navegue hasta ed417ae:

registro inicial

Luego rpresione para abrir la ventana emergente rebase

popup rebase

y mpara modificar el compromiso en el punto.

Observe cómo @hay ahora en la confirmación que desea dividir; eso significa que HEAD ahora está en esa confirmación:

modificando un commit

Queremos mover HEAD al padre, así que navegue al padre (47e18b3) y presione x( magit-reset-quickly, vinculado osi está usando evil-magit) e ingrese para decir "sí, me refería a confirmar en el punto". Su registro ahora debería verse así:

iniciar sesión después de reiniciar

Ahora, qpresione para ir al estado normal de Magit, luego use el ucomando regular de unstage para destrabar lo que no sucede en el primer commit, confirme cel resto como de costumbre, luego tage sy comita lo que sucede en el segundo commit, y cuando haya terminado: golpeado rpara abrir la ventana emergente de rebase

popup rebase

y otro rpara continuar, ¡y listo! llahora muestra:

todo hecho log

destrabar
fuente
1

Para dividir un commit <commit>y agregar el nuevo commit antes de este , y guardar la fecha del autor de <commit>, - los pasos son los siguientes:

  1. Edite el commit antes <commit>

    git rebase -i <commit>^^
    

    NB: quizás también será necesario editar <commit>.

  2. Cherry pick <commit>en el índice

    git cherry-pick -n <commit>
    
  3. Restablezca interactivamente los cambios innecesarios del índice y restablezca el árbol de trabajo

    git reset -p && git checkout-index -f -a
    

    Como alternativa, solo guarde los cambios innecesarios de forma interactiva: git stash push -p -m "tmp other changes"

  4. Realice otros cambios (si los hay) y cree la nueva confirmación

    git commit -m "upd something" .
    

    Opcionalmente, repita los elementos 2-4 para agregar más confirmaciones intermedias.

  5. Continuar rebasando

    git rebase --continue
    
ruvim
fuente
0

Hay una versión más rápida si solo desea extraer contenido de un solo archivo. Es más rápido porque el rebase interactivo ya no es interactivo (y, por supuesto, es aún más rápido si desea extraer del último commit, entonces no hay necesidad de rebase en absoluto)

  1. Use su editor y elimine las líneas de las que desea extraer the_file. Cerrar the_file. Esa es la única edición que necesitas, todo lo demás son solo comandos git.
  2. Etapa esa eliminación en el índice:

    git  add  the_file
    
  3. ¡Restaure las líneas que acaba de eliminar en el archivo sin afectar el índice !

    git show HEAD:./the_file > the_file
    
  4. "SHA1" es la confirmación de la que desea extraer las líneas:

    git commit -m 'fixup! SHA1' 
    
  5. Cree el segundo compromiso nuevo con el contenido para extraer restaurado en el paso 3:

    git commit -m 'second and new commit' the_file 
    
  6. No edite, no pare / continúe, simplemente acepte todo:

    git rebase --autosquash -i SHA1~1
    

Por supuesto, incluso más rápido cuando el commit para extraer es el último commit:

4. git commit -C HEAD --amend
5. git commit -m 'second and new commit' thefile
6. no rebase, nothing

Si lo usa magit, los pasos 4, 5 y 6 son una sola acción: Confirmar, Reparación instantánea

Marzo
fuente
-2

Si aún no has presionado, solo úsalo git rebase. Aún mejor, use git rebase -ipara mover commits de forma interactiva. Puedes mover el commit ofensivo al frente, luego dividirlo como quieras y mover los parches hacia atrás (si es necesario).

Gintautas Miliauskas
fuente
14
No hay necesidad de moverlo a ningún lado. Dividirlo donde está.
Cascabel
1
Desafortunadamente, esto no funciona para mí porque parte del historial después de la confirmación depende de él, por lo que estoy un poco restringido. Sin embargo, esta habría sido mi primera opción.
Ben
@Ben: está bien: las confirmaciones posteriores no tendrán que cambiar en absoluto (suponiendo que conserve todos los cambios, en lugar de descartar algunos de ellos). Más información aquí - stackoverflow.com/questions/1440050/…
Ether