Rebasar un commit de fusión Git

183

Tome el siguiente caso:

Tengo algo de trabajo en una rama temática y ahora estoy listo para volver a fusionarme con master:

* eb3b733 3     [master] [origin/master]
| * b62cae6 2   [topic]
|/  
* 38abeae 1

Realizo la fusión de master, resuelvo los conflictos y ahora tengo:

*   8101fe3 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
* | eb3b733 3                     [origin/master]
|/  
* 38abeae 1

Ahora, la fusión me llevó algo de tiempo, así que hago otra búsqueda y noto que la rama maestra remota tiene nuevos cambios:

*   8101fe3 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
| | * e7affba 4                   [origin/master]
| |/  
|/|   
* | eb3b733 3
|/  
* 38abeae 1

Si intento 'git rebase origin / master' desde master, me veo obligado a resolver todos los conflictos nuevamente, y también pierdo el commit de fusión:

* d4de423 2       [master]
* e7affba 4       [origin/master]
* eb3b733 3
| * b62cae6 2     [topic]
|/  
* 38abeae 1

¿Hay una manera limpia de volver a crear la confirmación de fusión para que termine con un historial como el que muestro a continuación?

*   51984c7 Merge branch 'topic'  [master]
|\  
| * b62cae6 2                     [topic]
* | e7affba 4                     [origin/master]
* | eb3b733 3
|/  
* 38abeae 1
jipumarino
fuente
74
TL; DR:git rebase --preserve-merges origin/master
Ilia K.
66
Con respecto a tener que volver a resolver los conflictos, es posible que desee echar un vistazo a git rerere .
Parker Coates
git config --global pull.rebase preserve para preservar siempre los compromisos de fusión durante un rebase
galath
44
Advertencia: a partir de Git 2.18 (Q2 2018, 5 años después), git --rebase-mergesfinalmente reemplazará al antiguo git --preserve-merges. Vea ¿Qué hace exactamente " rebase --preserve-merges" Git (y por qué?)
VonC
1
--preserve-mergeses obsoleto. Usogit rebase --rebase-merges origin/master
Arjun Sreedharan

Respuestas:

127

Aquí hay dos opciones.

Una de ellas es hacer un rebase interactivo y editar el commit de fusión, rehacer la fusión manualmente y continuar el rebase.

Otra es usar la --rebase-mergesopción on git rebase, que se describe de la siguiente manera en el manual: "Por defecto, un rebase simplemente eliminará los commits de fusión de la lista de tareas y colocará los commits rebaseados en una única rama lineal. Con --rebase- fusiones, la modificación intentará preservar la estructura de ramificación dentro de las confirmaciones que se van a modificar, recreando las confirmaciones de fusión. Cualquier conflicto de fusión resuelto o enmiendas manuales en estas confirmaciones de fusión tendrá que resolverse / volver a aplicarse manualmente ".

siride
fuente
16
Probé la opción -p, y de hecho deja el historial de confirmación como quería, pero me obliga a resolver los conflictos nuevamente, incluso en archivos que no fueron editados en origen / maestro. A partir de su primera sugerencia, ¿cuál sería la secuencia precisa de comandos?
jipumarino
2
@jipumarino: git rebase -i (dígale que edite el commit de fusión), cuando llegue al commit de fusión, git reset --hard HEAD ^, git merge, solucione conflictos, git commit, git rebase - continúe. También es posible que desee ver git rerere, que se supone que ayuda con este tipo de cosas (pero nunca lo he usado, por lo que no puedo ofrecer ningún consejo o ayuda).
siride
2
Gracias. Permití rerere e intenté con rebase -p y funciona como debería.
jipumarino
3
Aquí hay una excelente publicación de blog que describe esta situación exacta: Rebasar los compromisos de fusión en Git
kynan
1
rere no es la solución, ya que aún debe resolver las fusiones manualmente la primera vez.
Flimm
29

Ok, esa es una vieja pregunta y ya ha aceptado la respuesta @siride, pero esa respuesta no fue suficiente en mi caso, ya --preserve-mergesque te obliga a resolver todos los conflictos por segunda vez. Mi solución basada en la idea @Tobi Bpero con comandos exactos paso a paso

Entonces, comenzaremos con ese estado según el ejemplo de la pregunta:

*   8101fe3 Merge branch 'topic'  [HEAD -> master]
|\  
| * b62cae6 2                     [topic]
| |
| | * f5a7ca8 5                   [origin/master]
| | * e7affba 4
| |/  
|/|   
* | eb3b733 3
|/  
* 38abeae 1

Tenga en cuenta que tenemos 2 commits más adelante, por lo que cherry-pick no funcionaría.

  1. En primer lugar, creemos el historial correcto que queremos:

    git checkout -b correct-history # create new branch to save master for future
    git rebase --strategy=ours --preserve-merges origin/master
    

    Usamos --preserve-mergespara guardar nuestra confirmación de fusión en la historia. Solíamos --strategy=oursignorar todos los conflictos de fusión, ya que no nos importa qué contenido habrá en ese compromiso de fusión, solo necesitamos un buen historial ahora.

    La historia se verá así (ignorando al maestro):

    *   51984c7 Merge branch 'topic'  [HEAD -> correct-history]
    |\  
    | * b62cae6 2                     [topic]
    * | f5a7ca8 5                     [origin/master]
    * | e7affba 4
    * | eb3b733 3
    |/  
    * 38abeae 1
    
  2. Vamos a obtener el índice correcto ahora.

    git checkout master # return to our master branch
    git merge origin/master # merge origin/master on top of our master
    

    Podemos obtener algunos conflictos de fusión adicionales aquí, pero eso solo serían conflictos de archivos cambiados entre 8101fe3y f5a7ca8, pero no incluye conflictos ya resueltos detopic

    El historial se verá así (ignorando el historial correcto):

    *   94f1484 Merge branch 'origin/master'  [HEAD -> master]
    |\  
    * | f5a7ca8 5                   [origin/master]
    * | e7affba 4
    | *   8101fe3 Merge branch 'topic'
    | |\  
    | | * b62cae6 2                     [topic]
    |/ /
    * / eb3b733 3
    |/  
    * 38abeae 1
    
  3. La última etapa es combinar nuestra rama con el historial correcto y la rama con el índice correcto

    git reset --soft correct-history
    git commit --amend
    

    Usamos reset --softpara restablecer nuestra rama (e historial) para corregir el historial, pero dejamos el índice y el árbol de trabajo como está. Luego, usamos commit --amendpara reescribir nuestra confirmación de fusión, que solía tener un índice incorrecto, con nuestro buen índice del maestro.

    Al final tendremos dicho estado (tenga en cuenta otra identificación de top commit):

    *   13e6d03 Merge branch 'topic'  [HEAD -> master]
    |\  
    | * b62cae6 2                     [topic]
    * | f5a7ca8 5                     [origin/master]
    * | e7affba 4
    * | eb3b733 3
    |/  
    * 38abeae 1
    
Ivan Naydonov
fuente
¡Esto es increíble y ayudó mucho! Pero no entendí el truco con commit --amend: ¿podría agregar más información al respecto? ¿Qué sucede exactamente después de ejecutarlo? Noté que se modificó SHA de commit, pero ¿por qué? ¿O qué pasa si no ejecutas esto?
ZenJ
1
@ZenJ git commit --amendagrega los cambios a la última confirmación (HEAD, en este caso la confirmación de fusión). Debido a que el contenido de confirmación cambia, el hash se actualiza.
Dries Staelens
1
Para las personas que no tienen 'rerere' habilitado antes de solucionar conflictos, esta solución es excelente porque le evita tener que solucionar conflictos nuevamente. ¡Gracias!
Shackleford,
6

Dado que acabo de perder un día tratando de resolver esto y de hecho encontré una solución con la ayuda de un compañero de trabajo, pensé que debía intervenir.

Tenemos una base de código grande y tenemos que lidiar con 2 ramas que se modifican fuertemente al mismo tiempo. Hay una rama principal y una rama secundaria si usted.

Mientras fusiono la rama secundaria en la rama principal, el trabajo continúa en la rama principal y para cuando termino, no puedo impulsar mis cambios porque son incompatibles.

Por lo tanto, necesito "rebase" mi "fusión".

Así es como finalmente lo hicimos:

1) tome nota de la SHA. Ej .: c4a924d458ea0629c0d694f1b9e9576a3ecf506b

git log -1

2) Crea el historial apropiado pero esto romperá la fusión.

git rebase -s ours --preserve-merges origin/master

3) tome nota de la SHA. ej .: 29dd8101d78

git log -1

4) Ahora restablece a donde estabas antes

git reset c4a924d458ea0629c0d694f1b9e9576a3ecf506b --hard

5) Ahora combine el maestro actual en su rama de trabajo

git merge origin/master
git mergetool
git commit -m"correct files

6) Ahora que tiene los archivos correctos, pero el historial incorrecto, obtenga el historial correcto además de su cambio con:

git reset 29dd8101d78 --soft

7) Y luego: modifique los resultados en su confirmación de fusión original

git commit --amend

Voila!

Claude Peloquin
fuente
1

Parece que lo que quieres hacer es eliminar tu primera fusión. Puede seguir el siguiente procedimiento:

git checkout master      # Let's make sure we are on master branch
git reset --hard master~ # Let's get back to master before the merge
git pull                 # or git merge remote/master
git merge topic

Eso te daría lo que quieres.

Antoine Pelisse
fuente
44
Con rerere habilitado, esto parece dar el mismo resultado que la solución rebase -p dada anteriormente por siride.
jipumarino
0
  • De su compromiso de fusión
  • Elige el nuevo cambio que debería ser fácil
  • copia tus cosas
  • rehaga la fusión y resuelva los conflictos simplemente copiando los archivos de su copia local;)
Tobi B
fuente
1
Esta respuesta se ve bien, pero sería más útil si le diera los comandos git reales, en caso de que un usuario sea nuevo en git
Louise Davies