Cambiar la dirección de la fusión de git (después de que se haya completado la fusión)

Tengo una twig (por ejemplo, Feature-X) en un git repo.

Lo que hice fue lo siguiente

git checkout master git merge Feature-X 

Hubo bastantes conflictos que resolví. Aún no he comprometido los cambios.

Pero, resulta que lo que quería era hacer la fusión inversa (es decir, fusionar el master en Feature-x) porque la twig probablemente todavía es inestable.

¿Hay algún command que pueda salvar el trabajo que hice para resolver los conflictos o necesito hacer la resolución una vez más, esta vez en la twig Feature-X?

Estoy pensando si hay una manera de get el parche actual, pero aplicarlo en la twig Feature-X en el order "inverso". Por ejemplo, cuando el parche dice que la línea X cambió a Y, esto es relativo a Feature-X que va a dominar. Si quiero hacer lo contrario, el parche debería decir que la línea Y cambió a X, ¿verdad? Esto es solo mis pensamientos. Cualquier solución está bien.

TL; DR: ver el final

Hay una "receta" al final; desplazarse hacia abajo para encontrarlo (y luego upload un poco para encontrar la descripción y explicación y un método ligeramente más seguro).

buenas y malas noticias

En un sentido significativo, las fusiones no tienen exactamente una "dirección". (La dirección que "ves" cuando ves una fusión es producto de tu imaginación, más el post de fusión de compromiso, más una cosa más importante que serán nuestras "malas noticias", pero al final no es fatalmente malo). )

Considere este diagtwig de un par de twigs con commit * como su base de fusión:

  o--o--...--o <-- branch1 / ...--o--* \ o--o--...--o <-- branch2 

El process de fusión ("fusionar como un verbo") branch1 y branch2 se logra mediante:

  • comparando, es decir, git diff , la base de combinación commit * al commit de branch1 de branch1 ;
  • comparando la base de combinación con la punta de branch2 ;
  • luego, comenzando desde la base, combinando ambos sets de cambios.

Por lo tanto, la fusión real debería verse así:

  o--o--...--o / \ ...--o--* M \ / o--o--...--o 

(Llamé a la fusión cometer M ya que no sabemos, o realmente nos importa, qué loco ID de hash de Git deadbeef o badc0ffee o lo que sea que tenga). La eventual fusión commit M es la fusión ("fusionar como un sustantivo"); almacena el tree de fuente final, basado en el process de combinación fusión-como-un-verbo.

¿Qué "dirección" es esta fusión? Bueno, eso depende, al less en parte, de qué tags lo peguemos, ¿no? 🙂 Tenga en count que branch1 cuidadosamente tanto branch1 como branch2 . Vamos a ponerlos de vuelta:

  o--o--...--o <-- branch1? / \ ...--o--* M <-- branch1? branch2? \ / o--o--...--o <-- branch2? 

Esto puede parecer confuso. Probablemente sea confuso Es una gran parte de todo el problema. La mala noticia es que no es todo . Hay algo que no podemos captar en estos dibujos. Puede que no le importe, y si lo hace, no es un gran problema de todos modos; lo arreglaremos haciendo una confluencia de fusión más. Pero por ahora, sigamos adelante.

Haciendo el commit de fusión

Una vez que hacemos que la fusión se comprometa con M , tenemos que elegir (o hacer que Git elija) en qué twig se encuentra . En una fusión simple y sin conflictos, siempre terminamos haciendo que Git elija esto. Lo que hace Git es simple: en cualquier twig en la que estemos, cuando comenzamos el command de git merge , la twig que obtiene la combinación se compromete. Asi que:

 git checkout branch1 git merge branch2 

significa que la image final es:

  o--o--...--o / \ ...--o--* M <-- branch1 \ / o--o--...--o <-- branch2 

que podemos volver a dibujar como:

 ...--o--*--o--...--o--M <-- branch1 \ / o---...---o <-- branch2 

lo que hace obvio que fusionamos branch2 en branch1, no al revés. No obstante, el código fuente adjunto para confirmar M no depende de esta "dirección de fusión".

Barra lateral: fusiones conflictivas

Las fusiones en conflicto son como las fusiones sin conflictos en términos del resultado final. Es solo que Git no puede hacer la fusión por sí mismo. Se detiene y te hace resolver los conflictos, y luego ejecuta el git commit para hacer que la combinación se comprometa. Ese paso final de git commit hace fusionar commit M

La nueva confirmación de fusión va en la twig actual , al igual que cualquier otra confirmación. Así es como M termina en branch1 . Tenga en count que el post de la combinación de fusión también dice algo acerca de qué nombre de la twig se fusionó con el nombre de otra twig, pero tiene la posibilidad de editarlo mientras realiza la confirmación, por lo que puede cambiar eso para adaptarse a cualquier capricho que pueda tener. 🙂

(De hecho, esto no es diferente del caso no conflictivo: ambos ejecutan git commit para realizar la fusión final, y ambos le dan la oportunidad de editar el post).

Las malas noticias

La mala noticia es que estos diagtwigs omiten algo que puede interesarte. Git tiene una noción de primer padre: una fusión se compromete como M tiene dos padres, por lo que uno de ellos es el primer padre y el otro no. 1 Ese primer padre es cómo dices en qué twig estabas cuando hiciste la fusión.

Por lo tanto, aunque estos diagtwigs, y el process de fusión como un verbo, muestran que no hay exactamente una "dirección de fusión", esta noción de primer padre testing que sí existe. Si alguna vez usa esta noción de primer padre, le interesará esto y quiere hacerlo bien. Afortunadamente, hay una solución. (De hecho, hay múltiples soluciones, pero primero mostraré las klunky pero las sencillas).


1 Obviamente, el otro es el segundo padre: solo hay dos padres. Sin embargo, Git permite fusiones con tres o más padres. Puedes enumerar a todos los padres cuando sea necesario; pero Git considera que el primero es especialmente importante y tiene indicadores de "primer --first-parent para varios commands, para seguir "la twig original".


Obtener la fusión que queremos

Avancemos y hagamos la confluencia de fusión "incorrecta":

 # git add ... (if needed) git commit 

Ahora tenemos:

  o--o--...--o <-- [old branch1 tip] / \ ...--o--* M <-- branch1 \ / o--o--...--o <-- branch2 

donde el primer padre de M es el viejo consejo de branch1 .

Lo que queremos ahora es hacer una nueva confirmación de fusión (con ID diferente) que sea igual a M excepto que:

  • branch2 señala
  • su primer padre es la punta de branch2
  • su segundo padre es el consejo anterior de branch1

El truco es save la confirmación M alguna manera, es bastante fácil, utilizaremos un nombre temporal para no tener que copyr la ID hash de M y luego reiniciaremos la branch1 :

 git branch temp # or git tag temp git reset --hard HEAD~1 # move branch1 back, dropping M 

(tenga en count que esto es lo mismo que la respuesta de brehonia , hasta ahora). Ahora tenemos este gráfico, que no se modifica, excepto las tags adjuntas a cada confirmación:

  o--o--...--o <-- branch1 / \ ...--o--* M <-- temp \ / o--o--...--o <-- branch2 

Ahora branch2 y ejecuta la fusión otra vez; fallará con conflictos como antes:

 git checkout branch2 git merge branch1 # use --no-commit if Git would commit the merge 

El compromiso que aún no hemos realizado es el mismo, en cierto sentido, ya que la fusión confirma M pero una vez que lo logramos, su primer padre será el consejo actual de branch2 , y su segundo padre será el consejo actual de branch1 . Solo necesitamos get la fuente correcta para cumplir con este compromiso que vamos a realizar.

Ahora usamos el resultado de fusión que guardamos con el nombre temp . Esto supone que estás en el nivel superior de tu tree, de modo que . nombra todo:

 git rm -rf -- . # remove EVERYTHING git checkout temp -- . # get it all back from temp 

Ahora estamos utilizando el resultado de fusión que cometimos anteriormente. Ni siquiera tenemos que git add nada ya que esta forma de git checkout de git checkout actualiza el índice, así que:

 git commit 

y tenemos nuestra nueva combinación M2 , en la twig branch2 , como desee:

  o--o--...--o__ <-- branch1 / \ \ ...--o--* M \ <-- temp \ / \ o--o--...--o-----M2 <-- branch2 

Ahora podemos eliminar la twig o label temporal:

 git branch -D temp # or git tag -d temp 

y todos hemos terminado. Con el nombre temporal desaparecido, ya no podemos ver la fusión original M

Método de plomería sigiloso

De hecho, hay una manera más corta de hacer todo esto, usando los commands de "plomería" de Git, pero es un poco complicado. Una vez que tenemos todo git add -ed y hemos ejecutado git commit , tenemos el tree correcto, solo tenemos un commit que tiene el primer y el segundo ID padre incorrectos. También puede editar el post de confirmación, para que parezca que está fusionando branch1 en branch2 en el post. Entonces:

 # git add ... (if / as needed, as above) # git commit (make the merge) git branch -f branch2 $(git log --pretty=format:%B --no-walk HEAD | git commit-tree -p HEAD^2 -p HEAD^1 -F -) git reset --hard HEAD^1 

El paso git commit-tree hace una nueva fusión que es una copy de la que acabamos de hacer, pero con los dos padres intercambiados. Luego hacemos que branch2 apunte a este compromiso, usando git branch -f . Asegúrese de get el nombre ( branch2 ) en este paso. Los nombres HEAD^1 y HEAD^2 refieren a los dos (primero y segundo) padres de la confirmación actual , que es, por supuesto, la confirmación de fusión que acabamos de hacer. Eso tiene el tree correcto, y el derecho escribe el post de text; solo tiene el primer y segundo hash padre incorrectos.

Una vez que tenemos la nueva confirmación de security añadida a branch2 , simplemente reiniciamos nuestra twig actual ( branch1 ) para eliminar la confirmación de fusión que hicimos.

Confirme la fusión en el maestro, cree una nueva bifurcación en esa confirmación y restablezca la bifurcación principal a la confirmación anterior.

 git branch temp git reset --hard HEAD~1 

(Esto supone que no hay nada más en su copy de trabajo que desee conservar, ¡lo perdería en el reinicio!)

Has vuelto al lugar donde comenzaste (pre-fusión) pero las resoluciones de tus conflictos están seguras si las quieres.

Ahora intente fusionar de nuevo, el path correcto (maestro -> function). Si tiene suerte, no habrá tantos conflictos de esta manera y solo podrá hacerlo.

Si eso no es bueno para ti, podrías fusionar o seleccionar tu twig de temperatura en function. ¡No se verá bien en el gráfico, pero ese es el precio!

Recuerde borrar su twig adicional cuando haya terminado.

Puedes hacer varias cosas:

  1. hágalo manualmente usando git rebase -i e invierta manualmente el order de sus commits.

  2. escriba una secuencia de commands que git log --reverse y actualizará las confirmaciones en el order que desee.

  3. la próxima vez use git rebase --onto y decida dónde desea comenzar sus commits

--onto <newbase>
Punto de inicio en el que crear los nuevos commits. Si la opción –onto no está especificada, el punto de partida es. Puede ser cualquier confirmación válida, y no solo un nombre de twig existente.

Como caso especial, puede usar "A … B" como un acceso directo para la base de fusión de A y B si hay exactamente una base de fusión. Puede omitir como máximo uno de A y B, en cuyo caso por defecto HEAD.

enter image description here