¿Cómo aplastar dos commits no consecutivos?

183

Soy un poco nuevo en toda la función de rebase en git. Digamos que hice los siguientes commits:

A -> B -> C -> D

Después, me doy cuenta de que Dcontiene una solución que depende de algún código nuevo agregado A, y que estas confirmaciones van juntas. ¿Cómo aplastar Ay Djuntos y dejar By Csolo?

Nik Reiman
fuente

Respuestas:

258

Puede ejecutar git rebase --interactivey reordenar D antes de B y aplastar D en A.

Git abrirá un editor, y verá un archivo como este, por ejemplo: git rebase --interactive HEAD~4

pick aaaaaaa Commit A
pick bbbbbbb Commit B
pick ccccccc Commit C
pick ddddddd Commit D

# Rebase aaaaaaa..ddddddd onto 1234567 (4 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Ahora cambia el archivo que se ve así:

pick aaaaaaa Commit A
squash ddddddd Commit D
pick bbbbbbb Commit B
pick ccccccc Commit C

Y git ahora fusionará los cambios de A y D en un solo commit, y pondrá B y C después. Cuando no desee mantener el mensaje de confirmación de D, en lugar de squashusar la fixuppalabra clave. Para más información fixup, puede consultar los git rebasedocumentos o consultar esta pregunta que tiene algunas buenas respuestas.

Rudi
fuente
55
Inicialmente, lo leí como "rebase D en A, aplasta D en A, luego rebase B en DA". No queda claro en la respuesta que esto se puede hacer reordenando líneas en un editor de texto.
Victor Sergienko
3
Si su sucursal es local, recibirá un There is no tracking information for the current brancherror al volver a crear una nueva versión. En este caso es necesario especificar el número de confirmaciones que desea trabajar con, por ejemplo: git rebase -i HEAD~4. Mira esta respuesta .
johndodo
1
Uso el modo interactivo (git rebase -i) durante años, me acabo de dar cuenta de que se puede reordenar . Gracias 🤟🏻
CalvinChe
43

Nota: No debe cambiar las confirmaciones que se han enviado a otro repositorio de ninguna manera a menos que conozca las consecuencias .

git log --oneline -4

D commit_message_for_D
C commit_message_for_C
B commit_message_for_B
A commit_message_for_A

git rebase --interactive

pick D commit_message_for_D
pick C commit_message_for_C
pick B commit_message_for_B
pick A commit_message_for_A

Tipo i(poner VIM en modo de inserción)

Cambie la lista para que se vea así (no tiene que eliminar ni incluir el mensaje de confirmación). ¡No escribas mal squash! :

pick C commit_message_for_C
pick B commit_message_for_B
pick A commit_message_for_A
squash D

Escriba Escluego ZZ(Guardar y salir de VIM)

# This is a combination of 2 commits.
# The first commit's message is:

commit_message_for_D

# This is the 2nd commit message:

commit_message_for_A

Tipo i

Cambie el texto a cómo desea que se vea el nuevo mensaje de confirmación. Recomiendo que esta sea una descripción de los cambios en commit Ay D:

new_commit_message_for_A_and_D

Escribe EscentoncesZZ

git log --oneline -4

E new_commit_message_for_A_and_D
C commit_message_for_C
B commit_message_for_B

git show E

(You should see a diff showing a combination of changes from A and D)

Ahora ha creado una nueva confirmación E. Se compromete Ay Dya no está en su historia, pero no se ha ido. Todavía puede recuperarlos en este momento y durante un tiempo git rebase --hard Dgit rebase --harddestruirá cualquier cambio local! ).

Nate
fuente
3

Para aquellos que usan SourceTree :

Asegúrate de no haber empujado los commits.

  1. Repositorio> Rebase interactiva ...
  2. Arrastre D (la confirmación más reciente) para estar directamente encima de A (la confirmación más antigua)
  3. Asegúrese de que commit D esté resaltado
  4. Hacer clic Squash with previous
Adam Johns
fuente
1

El rebase interactivo funciona bien hasta que tenga una rama de características grandes con 20-30 confirmaciones y / o un par de fusiones del maestro o / y arregle conflictos mientras se comprometía en su rama. Incluso con la búsqueda de mis compromete a través de la historia y su sustitución pickcon squashno trabajado aquí. Así que estaba buscando otra forma y encontré este artículo . Hice mis cambios para trabajar esto en una rama separada:

git checkout master
git fetch
git pull
git merge branch-name
git reset origin/master
git branch -D branch-name
git checkout -b branch-name
git add --all
#Do some commit
git push -f --set-upstream origin branch-name

Antes de esto recibí mi solicitud de extracción con aproximadamente ~ 30 confirmaciones con 2-3 fusiones de maestro + solución de conflictos. Y después de esto obtuve relaciones públicas claras con una confirmación.

PS aquí es un script bash para hacer estos pasos en automode.

Oleksiy Guzenko
fuente
La primera solución en ese artículo es realmente agradable, gracias por el enlace
Hoody
-1

$ git checkout master

$ git log --en línea

D
C
B
A

$ git rebase --onto HEAD ^^^ HEAD ^

$ git log --en línea

D
A
Algodón
fuente
Creo que quieres decir --oneline? Y parece que has caído Cy B, que no es lo que pretendía el OP.
bstpierre
No funciono para mi. Movió mi CABEZA y mi maestro hacia A, pero no fusionó D en A ( git show A) y D, C y B se perdieron en mi registro de referencia. Tenía que git rebase Dvolver.
Nate