¿Es posible reescribir el historial de una sucursal sin perder información de fusión en el path?

Tenemos una twig principal en la que hemos fusionado aproximadamente 10 twigs de características, una a la vez.

Entonces, la historia reciente se ve así:

merged feat/10 (HEAD of master) merged feat/9 merged feat/8 merged feat/7 merged feat/6 merged feat/5 ... 

Ahora descubrimos que feat/7 era malo y queremos sacarlo de master. Revertir ese compromiso de fusión no es suficiente porque no queremos que ese compromiso roto exista en absoluto en nuestra historia. Realmente no podemos usar la database interactiva porque eso aplanará el historial para que parezca que todo se hizo en una sola twig, y ​​queremos preservar todo ese buen historial de fusión.

¿Hay alguna forma de eliminar una determinada fusión de la historia de una sucursal?

Notaré que la historia real es mucho más compleja de lo que se ve en el ejemplo anterior, por lo que volver a hacer manualmente todas las fusiones ya que feat / 7 no sería una buena opción.

Editar

Para aclarar a los que voten para cerrar esto como un duplicado: esta no es la pregunta frecuente sobre cómo sacar un compromiso con rebase, que por supuesto ha sido respondida muchas veces. La pregunta aquí es acerca de sacar una confirmación sin aplanar el historial de fusión.

Si tu historial actual se ve así y no borraste las twigs, puedes simplemente git reset --hard HEAD~4 Esto restablecerá tu código a estado antes de fusionarte en 7, entonces puedes simplemente git merge las buenas de nuevo. Esta es la manera más simple que puedo pensar de mi cabeza.

EDITAR: Puede usar -p para cambiar la database para conservar las fusiones, pero usar este modificador con -i puede tener consecuencias. Compruebe la página man git-rebase y vea la parte BUGS para ver los errores actuales.

EDIT2: No asumo ninguna responsabilidad si no tomas las precauciones adecuadas antes de usar este command. No lo use antes de leer la página de manual también.

Puede usar git filter-branch --parent-filter para reescribir el commit de feat/8 para que su padre apunte a la confirmación de feat/6 . Dejar a los padres de todos los demás compromisos (9-10) tal como están, lo que debería preservar los compromisos de fusión en la historia tal como eran.

El único problema con esto es lo que sucederá con los conflictos que provoquen que se modifique el código eliminado … no hay una forma real de saber, y podría ser el culpable.

Esto no es exactamente lo que quieres hacer ("borrar" el compromiso de fusión, por así decirlo), pero en términos prácticos es mucho más fácil que convencer a tus queueboradores para que se git reset --hard después de una rebase o filter-branch . Solo revierte la fusión.

 git revert -m 1 <commit_for_feat7> 

No me gusta particularmente contaminar mis twigs maestras con revertir, pero no hay nada intrínsecamente incorrecto en ello. Si no va a parchear feat7 por un time, o simplemente quiere que su cambio salga de la historia, esta solución es mucho less problemática que la revisión de la historia.

No puedes borrar cosas de git porque el hash actual depende de todo el historial.

Una opción es crear una nueva twig que comience desde feat / 6 y luego tenga las fusiones comenzando desde feat / 8 para que el branch head pueda apuntar a un hash diferente sin los cambios de feat / 7.

Otra opción, si no me equivoco, es git replace (creo que solía llamarse injertos). Puede permitirle "replace" el puntero en feat / 8 de feat / 7 a feat / 6. No estoy del todo seguro de cómo exactamente logra esto, pero parece que no es un verdadero reemploop porque feat / 8 todavía tiene un puntero en algún lugar para feat / 7 porque el hash no cambia, pero git replace alguna manera agrega una historia alternativa en la que feat / 8 puntos a feat / 6. Desde la página man :

 git replace [-f] <object> <replacement> git replace -d <object>… git replace -l [<pattern>] 

Agrega una reference de reemploop en .git / refs / replace /

El nombre de la reference de reemploop es SHA1 del object que se reemplaza. El contenido de la reference de reemploop es SHA1 del object de reemploop.

A less que se especifique -f, la reference de reemploop aún no debe existir en el directory .git / refs / replace /.

Las references de reemploop serán utilizadas por defecto por todos los commands de git, excepto aquellos que hacen un recorrido de accesibilidad (podar, transferencia de package y fsck).

Es posible desactivar el uso de references de reemploop para cualquier command usando la opción –no-replace-objects justo después de git.

EDITAR: en segundo lugar, git replace podría ser una mala idea ya que los cambios de feat / 7 existirían en la fusión de feat / 8. Probablemente deberías ir con la primera opción: comenzar una nueva twig de la feat / feat fusionada 6 y reiniciar desde feat / 8