Deshacer empujar la fusión git

Estoy en esta situación

\ / C | | BF | | [dev]AD \ / \ / M [Master] 

El maestro estaba en el compromiso D y este error ocurrió: la twig en el compromiso A se fusionó erróneamente en maestro y se empujó. Además, la confirmación M no contiene todos los cambios en A,B,C,... porque ha sido modificada a mano antes de comprometerse. Todos estos commits ya han sido empujados.

Necesito crear una nueva twig con los mismos cambios que en [dev] y luego fusionarla en master, en este momento no puedo porque esos commits ya se han fusionado. Además, todos los cambios en [dev] provienen de varias fusiones de diferentes twigs.

Importante limitación: mi server git no me permite forzar la inserción en la twig maestra, por lo que no puedo restablecer la twig maestra.

¿Hay alguna manera de poner cambios en [dev] en [master] después de una combinación incorrecta [M] ?

EDITAR: Se me permite forzar el empuje en twigs que no son maestras.

Entonces parece que los problemas son

1) La fusión aún no debería haber sucedido

2) La fusión se editó de una manera que significa que M no aplica todos los cambios dev sobre D

3) Eventualmente querrás fusionar dev (incluyendo A a C ) en master

4) Los empujes de fuerza no están permitidos en el master

Nos quedan opciones que no son geniales.

Para abordar (1) y (2), solo tiene que revertir M Debido a (4) no puede reescribir el historial de master , por lo que todos los methods para devolver el master a un estado que coincida con D son equivalentes a git revert . Eso resuelve (1) y hace (2) discutible.

Pero ahora (3) es un problema. La mayoría de las soluciones implican un empuje de fuerza para dev ; aunque no puede forzar empujar el master , no ha indicado de ninguna manera si se permite volver a escribir el historial de desarrollo. (Si no, tengan paciencia conmigo de todos modos, porque voy a build una solución para ese caso además de esto …)

Si la reescritura del historial de desarrollo está bien, entonces la pregunta es qué tan detallada es la historia que desea en la twig de reemploop (recordando que el historial original seguirá apareciendo, lo quiera o no, antes de la confirmación donde revierte M ).

La opción más simple que puedo pensar (pero la que tiene la menor fidelidad histórica en la nueva twig) se vería como

 git checkout D git branch -f dev git merge --squash C git commit -m "Replacement dev branch" git push -f 

Tenga en count que si desea editar manualmente el resultado de la "fusión de squash", puede hacerlo después del command de merge pero antes del command de commit ; pero, una vez más, entiendo que las ediciones hechas previamente fueron parte del problema …

Entonces, esto crea una única confirmación nueva enraizada en D , introduciendo los mismos cambios relativos a D como una fusión de C , pero sin la relación "2º padre" de una fusión real, lo que significa que git "olvidará" que estos cambios son los mismos como los cambios que ya combinados en master .

  \ / C | | BF | | AD \ / \ \ / \ M CBA [Dev] | W [Master] 

En este diagtwig, el TREE (contenido) en W coincide con D , y CBA coincide con lo que M habría sido si no hubiera sido editado incorrectamente. Podría haber rooteado CBA en el punto de derivación original del dev lugar de en D (eso está controlado por el command de checkout ); La única diferencia es si los conflictos existentes deben resolverse ahora o más adelante, dado que el historial va a estar "apagado" de cualquier manera.

Si tiene requisitos más específicos para lo que debería ser la "nueva historia" del desarrollador, dev y puedo sugerir un procedimiento que pueda acercarse a ellos.

En cualquier caso, debido a que esto reescribe el historial en dev , es mejor hacerlo cuando nadie ha implementado cambios en el desarrollador (de lo contrario, tendrán que volver a establecerse en la nueva twig de desarrollo). E incluso entonces, todos tendrán que forzar la actualización de su twig de desarrollo local. Consulte "recuperación desde el rebase ascendente" en los documentos de git rebase , porque eso es esencialmente lo que debe suceder.

Lo que me lleva a la otra posibilidad: si no puedes (o no quieres) reescribir el historial de desarrollo, entonces se necesitan pasos adicionales para asegurar que el desarrollador siempre se mueva de una manera "normal" (en lo que respecta al control remoto). ve).

 git checkout D git branch -f dev git merge --squash C git commit -m "Replacement dev branch" git rebase master git push 

Tenga en count que, aunque movemos la twig dev , luego la rebase convertir en un descendiente de origin/dev ; por lo que no se necesita ningún empuje de fuerza. Si esto parece inquietante, puede crear una twig temporal para la fusión de squash y luego realizar una simple combinación de ff para avanzar dev después de la rebase , pero el resultado es el mismo.

  \ / C | | BF | | AD \ / \ \ / \ M CBA | W [Master] / / CBA' [Dev] 

Tenga en count que CBA es inalcanzable y eventualmente se recolectará basura. Lo muestro aquí sin ningún motivo en particular. El nuevo desarrollador está en CBA' , una reescritura de CBA .

Nuevamente, si tiene requisitos específicos sobre cómo debe verse el historial entre W y CBA' avísenos y podremos intentar resolver un procedimiento, pero recuerde que el historial original aún es visible antes de M

Una combinación confusa de fusión siempre es difícil de solucionar, ya que no se puede (completamente) revertir una fusión para comenzar de nuevo. (Puede revertir los cambios que trajo, pero volver a fusionar no volverá a introducir estos cambios). Por lo general, la solución más fácil es reescribir la historia y empezar de nuevo, pero entiendo que eso no es posible.

La siguiente mejor opción es crear una confirmación de reparación que lleve el estado a la manera que desee. Si entendí tu pregunta correctamente, el problema es que la fusión no se realizó "por completo" (debido a ediciones en la fase de combinación), y realmente quieres que la combinación completa esté en el maestro.

En ese caso, yo:

 # Undo the merge, locally git checkout master git reset --hard D # the commitID from your graph, just before the merge # Re-do the merge correctly, locally git merge dev N=$(git rev-parse HEAD); echo $N # print as reference, call this commit N # restre to upstream state git reset --hard origin/master 

Ahora necesita crear un nuevo compromiso sobre el maestro M' , que lleva a M al estado deseado N

Tengo una solución para esa parte del problema, pero no estoy particularmente orgulloso de ello. Probablemente haya mejores forms de hacerlo, pero aquí va:

 # Figure out the tree-ID of the wanted state: TREE_ID=$( git cat-file -p $N | grep '^tree' | sed 's/tree //' ); echo $TREE_ID # Generate a commit with that tree ID. Ie all files will be exactly as # they are in commit N: everything you did in M to mangle the merge will be undone COMMIT_ID=$( echo "Fixup merge" | git commit-tree $TREE_ID -p HEAD ); echo $COMMIT_ID # ^^^^ this will be the commit message; change if needed # pull the generated commit in to the master branch git reset --hard $COMMIT_ID 

Después de fusionarse al realizar el checkout en su sucursal, ingrese el comentario mencionado más abajo en su masilla, donde la última vez que corrige el ID de confirmación ingrese en el lugar #commit_id. Advertencia: una vez que vuelva al código anterior, no podrá recuperar el último código de fusión.

 git revert #commit id 

Puede revertir la combinación, que revertir la restitución de la fusión, que restablecer la última confirmación, y elegir manualmente qué parches aplicar.

 git revert -m 1 <commit id> git revert HEAD git reset HEAD~1 

De lo que tendrías todos los cambios sin instancia de commit commit disponibles para editar con GUI o con:

 git add -i 

Con esto, podrías aplicar o restablecer parches de forma interactiva. O simplemente edita a mano en files.