Git: ¿Cómo convertir una `merge` existente a` merge –squash`?

merge --squash varios commit de merge pero deberían haber sido merge --squash en merge --squash lugar. La resolución del conflicto tomó más de un día, así que no puedo permitirme rehacer la fusión a mano.

¿Hay alguna manera de convertir la merge para merge --squash ?

Vale la pena señalar aquí que git merge y git merge --squash están estrechamente relacionados, pero git merge --squash no crea una fusión .

La networkingacción aquí es muy importante, particularmente el artículo "a" delante de "fusionar": "una fusión" es un nombre, mientras que "fusionar" es un verbo. Ambos commands realizan el acto de fusionarse. La diferencia radica en cómo se guarda el resultado .

También vale la pena un recordatorio rápido, en forma de un diagtwig de compromisos, de cómo se ven las fusiones. Cada ronda o nodo aquí representa una confirmación, con compromisos anteriores hacia la izquierda. Los nombres de las sucursales son tags que apuntan a una confirmación específica (la confirmación de una twig). Puede comenzar con esto, por ejemplo:

 ...--o--*--o--o <-- main \ o--o--o <-- feature 

Luego decide fusionar la twig de una feature específica de nuevo en la twig main , por lo que ejecuta:

 $ git checkout main && git merge feature 

Esto hace una fusión (verbo) y hace una combinación (sustantivo), y el resultado es así:

 ...--o--*--o--o---o <-- main \ / o--o--o <-- feature 

Git ha agregado una nueva confirmación a main , y esta nueva confirmación es una confirmación de fusión : apunta a la punta anterior de la main , y también a la anterior (y en este caso, hacia abajo) a la punta de la stream continua. . (La feature nombre continúa apuntando a la misma confirmación que antes).

Lo que git merge --squash hace es modificar el último paso. En lugar de realizar un commit de fusión, suprime el compromiso por completo, sin ninguna razón obvia, y te obliga a ejecutar el git commit . Cuando ejecutas git commit , esto hace una confirmación ordinaria , en lugar de una confirmación de fusión , para que el resultado se vea así:

 ...--o--*--o--o---o <-- main \ o--o--o <-- feature 

Hay dos elementos key aquí:

  • El contenido de los nuevos commits es el mismo. Ambos nuevos commits se realizan a partir del índice (el índice es el término de Git para "lo que entra en el próximo commit que hagas"). Este índice está configurado por el process de fusión-como-verbo. El primer padre es la confirmación de "línea principal", de la twig en la que estamos cuando hacemos la fusión. El segundo padre es el otro compromiso, el que acabamos de fusionar.

  • Los enlaces padre de los nuevos commits difieren. Una combinación real tiene ambos commits previos como sus padres, pero una "fusión de squash" tiene solo una confirmación previa como su compromiso principal: la "línea principal". Esto significa que no puede, no puede recordar qué compromiso se fusionó.

En el caso de una fusión conflictiva (pero real), la fusión de git merge no puede hacer la nueva confirmación por sí misma, por lo que se detiene y te obliga a resolver los conflictos. Una vez que hayas terminado de resolver esos conflictos, debes ejecutar git commit manualmente, como siempre (sin ninguna razón obvia) git merge --squash hacer con git merge --squash y sus fusiones falsas. También puede solicitar que se detenga cualquier fusión real y le permita inspeccionar el resultado, usando git merge --no-commit .

Esto lleva al método fácil de convertir una combinación real en una fusión falsa (squash), siempre y cuando la combinación real no se haya confirmado :

  • Para que git commit a saber hacer una fusión, se basa en un file dejado por la combinación conflictiva (o --no-commit ). Este file se llama .git/MERGE_HEAD . (También deja un file llamado .git/MERGE_MSG aunque este file adicional es inofensivo).

  • Por lo tanto, simplemente puede eliminar .git/MERGE_HEAD y ejecutar git commit . (También es posible que desee eliminar el file MERGE_MSG, una vez que haya escrito su confirmación con su post. Hasta entonces, puede usarlo como post o como punto de partida). El paso de git commit ya no sabrá hacer un commit de fusión, y en su lugar hará un commit-et voila ordinario, has hecho una fusión squash.


Si ya ha realizado una confesión de fusión real, el process puede ser más difícil. En particular, si ha publicado la fusión, ahora debe hacer que todos los que hayan obtenido esta fusión la recuperen. De lo contrario, la combinación real volverá (es probable que se fusionen de nuevo) o les creará problemas, o incluso ambos. Sin embargo, si puede hacerlo, o si no se ha publicado, simplemente necesita "apartar la fusión", por así decirlo, y realizar una fusión falsa.

Vamos a volver a dibujar lo que tenemos, para hacer algo de espacio. Este es el mismo gráfico que antes, simplemente distribuido en más líneas:

 ...--o--*----o----o \ \ \ o <-- main \ / o--o--o <-- feature 

¿Qué pasaría si pudiéramos mover la copy de security main a la línea superior y luego realizar una nueva confirmación? Bueno, obtendríamos el siguiente dibujo:

 ...--o--*----o----o--o <-- main \ \ \ o [abandoned] \ / o--o--o <-- feature 

Tenga en count que todas las flechas, incluidas las flechas de compromiso interno que los commits usan para registrar el historial (que no se muestran aquí porque son demasiado difíciles de producir en los dibujos de text) apuntan hacia la izquierda , por lo que no hay conocimiento en ninguna de las líneas principales commits, de cualquiera de los commits debajo de ellos: todos están "a su derecha". Solo los compromisos finales, más el que vamos a abandonar, saben algo acerca de los compromisos de la línea superior.

Además, si vamos a abandonar por completo el compromiso de la línea media, dejamos de dibujarlo, y sus dos flechas de fusión:

 ...--o--*----o----o--o <-- main \ \ \ o--o--o <-- feature 

Y mira eso: acabamos de dibujar el tipo de gráfico de compromiso que queremos para una de esas "fusiones" de squash falsas. Esto es justo lo que queremos, siempre y cuando obtengamos los contenidos correctos en nuestro nuevo compromiso.

Pero, esto es una especie de ejercicio; mira si sabes la respuesta antes de sumergirte, ¿de dónde vienen los contenidos de un nuevo compromiso? La respuesta está arriba, en negrita, en el primer punto. (Si lo ha olvidado, regrese y verifique).


Ahora que sabes que git commit usa el índice para realizar el nuevo compromiso, consideremos el compromiso real de fusión que tenemos ahora. Tiene contenido. (Todos los commits tienen contenido.) ¿De dónde vienen? ¡Vinieron del índice! Si podemos volver a poner esos contenidos en el índice de alguna manera, estamos dorados. Y, de hecho, podemos: todo lo que tenemos que hacer es verificar que commit, con git checkout , o ya lo tenemos como commit actual .

Como acabamos de realizar la nueva combinación de compromiso, ya tenemos el compromiso de fusión como el compromiso actual. El índice está limpio: si ejecutamos el git status , dice que no hay nada que comprometer. Así que ahora usamos git reset --soft para restablecer el puntero de la twig main en un paso atrás, para get nuestro dibujo intermedio. El argumento --soft dice a Git: "mueva el puntero de la bifurcación, pero no cambie el índice y el tree de trabajo". Así que todavía tendremos el índice (y el tree de trabajo) de la combinación original. Ahora solo ejecutamos git commit para hacer una confirmación ordinaria, enviamos un post de confirmación apropiado, y terminamos: tenemos una combinación de squash. La fusión original ahora está abandonada, y finalmente, en algún momento después de 30 días, por defecto, Git notará que ya no se usa y lo eliminará.

(Para retroceder un paso, puede usar HEAD~1 o HEAD^ ; ambos significan lo mismo. Por lo tanto, la secuencia de commands es simplemente git reset --soft HEAD^ && git commit , suponiendo que el commit actual es la fusión real que desea para replace con una fusión falsa).


El método anterior más arriba se puede usar incluso si realizó varias confusiones de fusión. Sin embargo, tendrá que decidir si desea múltiples fusiones falsas o una gran combinación falsa. Por ejemplo, supongamos que tienes esto:

 ...--o--o---o--o--o <-- develop \ \ / / \ o--o / <-- feature1 \ / o----o <-- feature2 

¿Quieres que se vea tu foto final?

 ...--o--o---o--o--o <-- develop \ \ \ o--o <-- feature1 \ o----o <-- feature2 

donde los últimos dos compromisos de develop son las dos fusiones falsas (squash), o solo quieres una gran calabaza falsa:

 ...--o--o---o--o <-- develop \ \ \ o--o <-- feature1 \ o----o <-- feature2 

donde ese último compromiso es el resultado final? Si desea dos falsas calabazas, deberá conservar las dos confusiones de fusión originales el time suficiente para includelas en el índice y hacer dos compromisos ordinarios a partir de ellas. Si solo quieres una gran falsa calabaza de la fusión final, eso solo requiere dos commands de Git:

 $ git reset --soft HEAD~2 && git commit 

ya que conservaremos el índice de la segunda fusión, retrocederemos dos pasos y luego haremos la nueva confirmación que deja de lado ambas fusiones.

De nuevo, es importante que ninguna de las fusiones reales se haya publicado, o, si se publican, que convenzas a todos los que las han recogido de que dejen de usarlas.