¿Las solicitudes de extracción de aplastamiento rompen el algoritmo de fusión de git?

16

Actualmente estoy trabajando para una empresa que usa VSTS para administrar el código git. La forma "recomendada" de Microsoft de fusionar una sucursal es hacer una "fusión de squash", lo que significa que todas las confirmaciones para esa rama se aplastan en una nueva confirmación que incorpora todos los cambios.

El problema es, ¿qué sucede si hago algunos cambios en una rama para un elemento de la cartera de pedidos, y de inmediato quiero comenzar a hacer cambios en otra rama para otro elemento de la cartera de pedidos, y esos cambios dependen del conjunto de cambios de la primera rama?

Puedo crear una rama para ese elemento acumulado y basarlo en la primera rama. Hasta aquí todo bien. Sin embargo, cuando llega el momento de crear una solicitud de extracción para mi segunda rama, la primera rama ya se ha fusionado en master y debido a que se ha hecho como una fusión de squash, git señala un montón de conflictos. Esto se debe a que git no ve los commits originales en los que se basó la segunda rama, solo ve la fusión de una gran calabaza y, por lo tanto, para fusionar la segunda rama en master, intenta reproducir todos los commits de la primera rama en la parte superior de la calabaza se fusiona, causando muchos conflictos.

Entonces, mi pregunta es, ¿hay alguna forma de evitar esto (aparte de no basar nunca una rama de función en otra, lo que limita mi flujo de trabajo) o la fusión de squash solo rompe el algoritmo de fusión de git?

Jez
fuente

Respuestas:

15

Con Git, se compromete

  • son inmutables
  • y formar un gráfico acíclico dirigido.

El aplastamiento no combina commits. En cambio, registra una nueva confirmación con los cambios de varias otras confirmaciones. Rebasar es similar, pero no combina commits. La grabación de una nueva confirmación con los mismos cambios que una confirmación existente se llama reescritura del historial . Pero como los commits existentes son inmutables, esto debe entenderse como "escribir una historia alternativa".

La fusión intenta combinar los cambios de las historias de dos commit (ramas) a partir de un commit de antepasado común.

Así que echemos un vistazo a su historia:

                                 F  feature2
                                /
               1---2---3---4---5    feature1 (old)
              /
-o---o---o---A---o---o---S          master

A es el ancestro común, 1–5 la rama de la característica original, F la nueva rama de la característica y S la confirmación aplastada que contiene los mismos cambios que 1–5. Como puede ver, el ancestro común de F y S es A. En lo que respecta a git, no hay relación entre S y 1–5. Por lo tanto, fusionar master con S en un lado y feature2 con 1–5 en el otro entrará en conflicto. Resolver estos conflictos no es difícil, pero es un trabajo innecesario y tedioso.

Debido a estas limitaciones, hay dos enfoques para tratar con la fusión / aplastamiento:

  • O usa la reescritura del historial, en cuyo caso obtendrá múltiples confirmaciones que representan los mismos cambios. Luego volvería a basar la segunda rama de la característica en la confirmación aplastada:

                                     F  feature2 (old)
                                    /
                   1---2---3---4---5    feature1 (old)
                  /
    -o---o---o---A---o---o---S          master
                              \
                               F'       feature2
    
  • O no utiliza la reescritura del historial, en cuyo caso puede obtener confirmaciones de fusión adicionales:

                                     F  feature2
                                    /
                   1---2---3---4---5    feature1 (old)
                  /                 \
    -o---o---o---A---o---o-----------M  master
    

    Cuando se fusionan feature2 y master, el ancestro común se comprometerá 5.

En ambos casos, tendrá un esfuerzo de fusión. Este esfuerzo no depende mucho de cuál de las dos estrategias anteriores elija. Pero asegúrate de que

  • las ramas son de corta duración, para limitar qué tan lejos pueden derivarse de la rama maestra, y eso
  • regularmente fusionas master en tu rama de características, o cambias la rama de características en master para mantener las ramas sincronizadas.

Cuando se trabaja en un equipo, es útil coordinar quién está trabajando actualmente en qué. Esto ayuda a mantener pequeña la cantidad de características en desarrollo y puede reducir la cantidad de conflictos de fusión.

amon
fuente
2
Su respuesta no parece abordar lo que sucede si primero fusiona la calabaza feature1con el maestro y luego desea fusionarse feature2más tarde. En ese caso, ¿el primer enfoque no daría lugar a conflictos ya que git intenta volver a aplicar los feature1commits sobre el commit aplastado, mientras que el segundo permitiría que git determine que esos commits no necesitan fusionarse?
Jez
@Jez Eso es exactamente lo que sucede cuando aplastas un RP. Recientemente tuve que reescribir manualmente un RP en un proyecto OSS (¡ git cloneingresando el repositorio y copiando mis archivos modificados!) Porque me bifurqué de una rama y luego el responsable aplastó la primera rama. En mi trabajo también hacen fusiones de squash. Esto significa que no puedo trabajar en una función bque depende de la función ahasta que ase combine.
Deseche la cuenta
1
¿Y no es una ruptura realmente molesta de algo que de otro modo funcionaría, como está diseñado git? Mira, veo que varias organizaciones como Microsoft y Github recomiendan estas fusiones de squash y me parecen tontas.
Jez
1
@Jez En el escenario original, sí, obtendrá conflictos al fusionar feature2 en master porque la fusión de los commits 1–5 entrará en conflicto con los mismos cambios en S. La solución es rebase de la característica2 (solución 1) o no usar aplastar / rebase en todos (solución 2).
amon
Si las fusiones de squash son adecuadas para usted, depende de lo que quiera grabar en el historial de control de versiones. Si las ramas de características tienen muchas confirmaciones WIP, entonces el aplastamiento coloca una sola confirmación grande con la característica completa en la rama maestra. Si prefiere preservar todas las confirmaciones intermedias de la rama de características, use rebasar o fusionar.
amon
11

La fusión de squash rompe el algoritmo de fusión de cualquier rama que contenga confirmaciones que fueron eliminadas por la calabaza. Dicho de otra manera, los rebases son virales. Si cambia la base de una rama, debe cambiar las ramas que dependen de esa rama. Si usa rerere , los conflictos de fusión que resolvió manualmente en su repositorio local no necesitarán resolverse manualmente nuevamente, pero eso no ayuda con los conflictos resueltos por otras personas.

Es por eso que nuestra regla no escrita aquí es que está bien aplastar siempre y cuando nadie más dependa de su rama de características, que es el caso tal vez el 90% del tiempo. De lo contrario, una fusión directa ayuda a todos a evitar problemas.

Karl Bielefeldt
fuente
Una forma de tener una confirmación aplastada en la historia maestra y una rama característica intacta para hacer una rama separada solo de calabaza. Supongamos que tienes una feature-xyzrama. Se puede crear una feature-xyz-squashedrama a partir de la misma se comprometan como feature-xyzrama, git cherry-picklas confirmaciones de feature-xyzque feature-xyz-squashed, a aplastar allí, y de combinación feature-xyz-squasheda master. No deberías fusionarte feature-xyzentonces. A veces, lo anterior tiene sentido (por ejemplo, no desea incluir confirmaciones con una contraseña introducida), pero es una solución alternativa, difícilmente una mejor práctica.
9000