Explicación del flujo de Git mejorado

Publicado: 2022-03-11

Causar daño sin darse cuenta con Git puede ser demasiado fácil. Sin embargo, la mejor manera de usar Git siempre será controvertida.

Esto se debe a que Git solo detalla las operaciones básicas de bifurcación, lo que deja sus patrones de uso, es decir, los modelos de bifurcación, en una cuestión de opinión del usuario. Los modelos de ramificación de Git prometen aliviar el dolor al organizar el caos que surge inevitablemente cuando los desarrolladores de software realizan cambios en sus bases de código.

Al igual que muchos desarrolladores, quería algo que "simplemente funcionara" para poder continuar con el desarrollo de software real. Entonces eligió Git flow , un modelo de ramificación que a menudo se recomienda a los usuarios de Git. Tal vez estabas de acuerdo con la lógica de flujo de Git al principio, hasta que te encontraste con algunos inconvenientes en la práctica. O tal vez el flujo de Git no parezca lo suficientemente bueno como para que lo adoptes. Después de todo, hay innumerables variables en juego y ningún modelo de bifurcación único funcionará bien en todas las situaciones.

¡Buenas noticias! Una variación del modelo de flujo de Git clásico, el flujo de Git mejorado simplifica las maniobras más comunes del flujo de trabajo de Git al mismo tiempo que conserva las principales ventajas.

El esplendor y la miseria del modelo de flujo de Git clásico

He sido un gran defensor del flujo de Git desde que descubrí cómo sobresale al desarrollar un producto que evoluciona en incrementos de valor significativos (en otras palabras, lanzamientos ).

Un incremento de valor significativo requiere una cantidad significativa de tiempo para completarse, como los sprints de más de dos semanas que se usan normalmente en el desarrollo basado en Scrum. Si un equipo de desarrollo ya se ha implementado en producción, puede haber problemas si el alcance de la próxima versión se acumula en el mismo lugar donde vive el código de producción, por ejemplo, en la rama principal del repositorio de Git que utilizan.

Si bien el producto aún se encuentra en la fase de desarrollo inicial, es decir, no hay producción y no hay usuarios reales del producto, está bien que el equipo simplemente mantenga todo dentro de la rama principal. De hecho, está más que bien: esta estrategia permite el ritmo de desarrollo más rápido sin mucha ceremonia. Pero las cosas cambian en un entorno de producción; luego, las personas reales comienzan a confiar en que el producto sea estable.

Por ejemplo, si hay un error crítico en producción que debe corregirse de inmediato, sería un gran desastre para el equipo de desarrollo tener que revertir todo el trabajo acumulado en la rama principal hasta el momento solo para implementar la corrección. E implementar el código sin las pruebas adecuadas, ya sea que el código se considere a medio hacer o bien desarrollado, claramente no es una opción.

Ahí es donde brillan los modelos de ramificación, incluido el flujo de Git. Cualquier modelo de bifurcación sofisticado debe responder preguntas sobre cómo aislar la próxima versión de la versión del sistema que utilizan actualmente las personas, cómo actualizar esa versión con la próxima versión y cómo introducir revisiones de cualquier error crítico en la versión actual.

El proceso de flujo de Git aborda estos escenarios fundamentales al separar "principal" (la rama de producción o "versión actual") y "desarrollar" (la rama de desarrollo o "próxima versión") y proporciona todas las reglas sobre el uso de ramas de función/lanzamiento/revisión . Resuelve de manera efectiva muchos dolores de cabeza de los flujos de trabajo de desarrollo de productos basados ​​en versiones.

Pero incluso con proyectos que se adaptan bien al modelo de flujo clásico de Git, he sufrido los problemas típicos que puede traer:

  • El flujo de Git es complejo, con dos ramas de larga duración, tres tipos de ramas temporales y reglas estrictas sobre cómo las ramas se relacionan entre sí. Tal complejidad hace que los errores sean más probables y aumenta el esfuerzo necesario para corregirlos.
  • Las ramas Release y Hotfix requieren una "doble combinación": una vez en Main y luego en Develop. A veces puedes olvidarte de hacer ambas cosas. Puede facilitar la bifurcación del flujo de Git con scripts o complementos de cliente de GUI de VCS, pero primero debe configurarlos para cada máquina de cada desarrollador involucrado en un proyecto determinado.
  • En los flujos de trabajo de CI/CD, generalmente termina con dos compilaciones finales para una versión: una de la última confirmación de la propia rama de versión y otra de la confirmación de combinación con main. Estrictamente hablando, debe usar el principal, pero los dos suelen ser idénticos, lo que crea la posibilidad de confusión.

Ingrese "Flujo de Git mejorado"

La primera vez que utilicé el flujo mejorado de Git fue en un proyecto de código cerrado completamente nuevo. Estaba trabajando con otro desarrollador y habíamos estado trabajando en el proyecto comprometiéndonos directamente con la rama principal.

Nota: Hasta el primer lanzamiento público de un producto, tiene mucho sentido enviar todos los cambios directamente a la rama principal, incluso si es un defensor del flujo de Git, por el bien de la velocidad y la simplicidad del flujo de trabajo de desarrollo. Dado que aún no hay producción, no hay posibilidad de un error de producción que el equipo deba corregir lo antes posible. Por lo tanto, hacer toda la magia de ramificación que implica el flujo clásico de Git es excesivo en esta etapa.

Luego nos acercamos al lanzamiento inicial y acordamos que, más allá de ese punto, ya no nos sentiríamos cómodos comprometiéndonos directamente con la rama principal. Nos habíamos movido bastante rápido y las prioridades comerciales no dejaban mucho espacio para establecer un proceso de desarrollo sólido como una roca, es decir, uno con suficientes pruebas automatizadas para darnos la confianza de mantener nuestra sucursal principal en un estado listo para su lanzamiento.

Parecía ser un caso válido para el modelo de flujo clásico de Git. Con sucursales principales y de desarrollo separadas y suficiente tiempo entre incrementos de valor significativos, había confianza en que el control de calidad mayormente manual arrojaría resultados suficientemente buenos. Cuando abogué por el flujo de Git, mi colega sugirió algo similar, pero con algunas diferencias clave.

Al principio, retrocedí. Me pareció que algunos de los "parches" propuestos para el flujo clásico de Git eran demasiado revolucionarios. Pensé que podrían romper la idea principal y que todo el enfoque se quedaría corto. Pero pensándolo mejor, me di cuenta de que estos ajustes en realidad no interrumpen el flujo de Git. Mientras tanto, lo convierten en un mejor modelo de ramificación de Git al resolver todos los puntos débiles mencionados anteriormente.

Después del éxito con el enfoque modificado en ese proyecto, lo usé en otro proyecto de código cerrado con un pequeño equipo detrás, donde yo era el propietario permanente de la base de código y uno o dos desarrolladores subcontratados ayudaban de vez en cuando. En este proyecto, entramos en producción seis meses después y, desde entonces, hemos estado usando pruebas de CI y E2E durante más de un año, con lanzamientos cada mes más o menos.

Un gráfico de confirmación de Git típico cuando se usa un flujo de Git mejorado. El gráfico muestra algunas confirmaciones en desarrollo y principal, y antes de su confirmación común, varias etiquetas basadas en fechas.

Mi experiencia general con este nuevo enfoque de bifurcación fue tan positiva que quería compartirla con mis compañeros desarrolladores para ayudarlos a solucionar los inconvenientes del flujo de Git clásico.

Similitudes con Classic Git Flow: aislamiento de desarrollo

Para el aislamiento del trabajo en el flujo de Git mejorado, todavía hay dos ramas de larga duración, principal y de desarrollo. (Los usuarios todavía tienen capacidades de revisión y lanzamiento, con énfasis en las "capacidades", ya que ya no son ramas. Entraremos en detalles en la sección de diferencias).

No existe un esquema de nombres oficial para las ramas de funciones de flujo de Git clásicas. Simplemente se ramifica desde el desarrollo y se fusiona nuevamente para desarrollar cuando la función está lista. Los equipos pueden usar cualquier convención de nomenclatura que deseen o simplemente esperar que los desarrolladores usen nombres más descriptivos que "mi-sucursal". Lo mismo es cierto para el flujo mejorado de Git.

Todas las funciones acumuladas en la rama de desarrollo hasta cierto punto de corte darán forma a la nueva versión.

Combinaciones de calabaza

Recomiendo encarecidamente usar squash merge para ramas de funciones para mantener el historial agradable y lineal la mayor parte del tiempo. Sin él, los gráficos de confirmación (desde herramientas GUI o git log --graph ) comienzan a verse descuidados cuando un equipo está haciendo malabarismos incluso con un puñado de ramas de características:

Comparación del gráfico de confirmación resultante de una estrategia de fusión de squash con el resultado de una estrategia de confirmación de fusión. El gráfico inicial de confirmación de Git tiene su rama principal etiquetada como "desarrollar", con una confirmación anterior que tiene una ramificación de "característica-D" y "característica-C", y una confirmación aún anterior que tiene "característica-B" y "característica-A". " bifurcándose de él. El resultado de la confirmación de combinación es similar, pero con cada rama de función que genera una nueva confirmación en el punto en el que se vincula con ella. El resultado de squash merge se ha desarrollado simplemente como una línea recta de confirmaciones, sin otras ramificaciones.

Pero incluso si está de acuerdo con las imágenes en este escenario, hay otra razón para aplastar. Sin aplastar, las vistas del historial de confirmación, entre ellas tanto el git log simple (sin --graph ) como también GitHub, cuentan historias bastante incoherentes incluso con los escenarios de combinación más simples:

Comparación de la vista del historial de confirmaciones resultante de una táctica de fusión de squash con la resultante de una táctica de confirmación de fusión. El estado del repositorio original se proporciona en forma de línea de tiempo, mostrando la cronología de las confirmaciones en dos ramas de características que provienen de una confirmación común "x" en desarrollo, alternando confirmaciones entre las ramas, es decir, en el orden 1a, 2a, 1b y 2b. Los resultados de la confirmación de fusión se muestran en dos variaciones. En el historial de confirmaciones resultante de fusionar la segunda rama, fusionar la primera rama y luego eliminar ambas ramas, la historia es cronológica, pero no cohesiva, e incluye una confirmación de fusión adicional para la primera rama. En el historial resultante de fusionar las ramas en orden antes de eliminarlas, los compromisos se ordenan, "x, 2a, 1a, 2b, 1b", seguidos del compromiso de fusión para la segunda rama, que ni siquiera es cronológica. El historial de confirmaciones de la combinación de squash simplemente tiene una única confirmación para cada rama de características, con la historia de cada rama contada por el autor de la confirmación.

La advertencia de usar la fusión de squash es que el historial de bifurcación de características original se pierde. Pero esta advertencia ni siquiera se aplica si está utilizando GitHub, por ejemplo, que expone el historial original completo de una rama de funciones a través de la solicitud de extracción que se fusionó, incluso después de que se eliminó la rama de funciones.

Diferencias con Classic Git Flow: versiones y revisiones

Repasemos el ciclo de lanzamiento ya que (con suerte) es lo principal que harás. Cuando llegamos al punto en el que nos gustaría publicar lo que se acumuló en el desarrollo, es estrictamente un superconjunto de main. Después de eso es donde comienzan las mayores diferencias entre el flujo de Git clásico y mejorado.

Gráficas de confirmación de Git a medida que cambian cuando se realiza una versión normal con un flujo de Git mejorado. El gráfico inicial tiene divergencias principales de desarrollar varios compromisos detrás de la punta y por un compromiso. Después de etiquetar, main y vYYYY-MM-DD están parejos entre sí. Después de eliminar el principal local, crearlo en la punta de desarrollo, empujar forzosamente, implementar, probar, etc., el principal se muestra incluso con el desarrollo, dejando vYYYY-MM-DD donde había estado originalmente el principal. Después del ciclo de implementación/prueba, las correcciones de preparación en main (eventualmente, squash se fusionó con el desarrollo) y, mientras tanto, los cambios no relacionados en el desarrollo, el gráfico final se ha desarrollado y el principal diverge, cada uno con varias confirmaciones, desde donde eran iguales entre sí en el gráfico anterior.

Versiones en Enhanced Git Flow

Cada paso para hacer un lanzamiento con el flujo de Git mejorado difiere del proceso de flujo de Git clásico:

  1. Los lanzamientos se basan en principal, en lugar de desarrollar. Etiquete la punta actual de la rama principal con algo significativo. Adopté etiquetas basadas en la fecha actual en formato ISO 8601 con el prefijo "v", por ejemplo, v2020-09-09 .
    • Si hubiera varios lanzamientos en un día, por ejemplo, revisiones, el formato podría tener un número secuencial o una letra, según sea necesario.
    • Tenga en cuenta que las etiquetas, en general, no se corresponden con las fechas de lanzamiento. Son únicamente para obligar a Git a mantener una referencia de cómo se veía la rama principal en el momento en que comenzó el próximo proceso de lanzamiento.
  2. Empuje la etiqueta usando git push origin <the new tag name> .
  3. Después de eso, un poco de sorpresa: elimine su rama principal local . No te preocupes, porque lo vamos a restaurar en breve.
    • Todas las confirmaciones de main siguen siendo seguras; las protegemos de la recolección de elementos no utilizados al etiquetar main en el paso anterior. Cada una de estas confirmaciones, incluso las revisiones, como veremos en breve, también forma parte del desarrollo.
    • Solo asegúrese de que solo una persona en un equipo esté haciendo esto para cualquier lanzamiento dado; ese es el llamado rol de "administrador de versiones". Un gerente de lanzamiento suele ser el miembro del equipo con más experiencia y/o mayor rango, pero sería prudente evitar que un miembro del equipo en particular asumiera este rol de forma permanente. Tiene más sentido difundir el conocimiento entre el equipo para aumentar el infame factor del autobús.
  4. Cree una nueva rama principal local en la confirmación de punta de su rama de desarrollo .
  5. Empuje esta nueva estructura usando git push --force , ya que el repositorio remoto no aceptará un "cambio tan drástico" tan fácilmente. Nuevamente, esto no es tan inseguro como puede parecer en este contexto porque:
    • Simplemente estamos moviendo el puntero de la rama principal de una confirmación a otra.
    • Solo un miembro del equipo en particular está haciendo este cambio a la vez.
    • El trabajo de desarrollo diario se lleva a cabo en la rama de desarrollo, por lo que no interrumpirá el trabajo de nadie moviéndose principal de esta manera.
  6. ¡Ya tienes tu nuevo lanzamiento! Despliéguelo en el entorno de ensayo y pruébelo. (Discutiremos los patrones convenientes de CI/CD a continuación). Cualquier corrección va directamente a la rama principal, y comenzará a divergir de la rama de desarrollo debido a eso.
    • Al mismo tiempo, puede comenzar a trabajar en una nueva versión en la rama de desarrollo, la misma ventaja que se ve en el flujo de Git clásico.
    • En el desafortunado caso de que se necesite una revisión para lo que está actualmente en producción (no para el próximo lanzamiento en preparación) en este punto, hay más detalles sobre este escenario en "Tratar con revisiones durante una versión activa..." a continuación.
  7. Cuando su nueva versión se considere lo suficientemente estable, implemente la versión final en el entorno de producción y haga una sola combinación de squash de main para desarrollar para recoger todas las correcciones.

Revisiones en Enhanced Git Flow

Los casos de revisión son dobles. Si está haciendo una revisión cuando no hay una versión activa , es decir, el equipo está preparando una nueva versión en la rama de desarrollo, es muy fácil: Comprométase con main, implemente sus cambios y pruébelos en staging hasta que estén listos, luego implementar en producción.

Como último paso, seleccione su compromiso de principal a desarrollo para asegurarse de que la próxima versión contenga todas las correcciones. En caso de que termine con varias confirmaciones de revisión, ahorrará esfuerzo, especialmente si su IDE u otra herramienta Git puede facilitarlo, al crear y aplicar un parche en lugar de elegir varias veces. Es probable que tratar de aplastar merge main para desarrollar después del lanzamiento inicial genere conflictos con el progreso independiente realizado en la rama de desarrollo, por lo que no lo recomiendo.

Lidiar con las revisiones durante una versión activa , es decir, cuando acaba de presionar principal y todavía está preparando la nueva versión, es la parte más débil del flujo mejorado de Git. Dependiendo de la duración de su ciclo de lanzamiento y la gravedad del problema que tiene que resolver, siempre trate de incluir correcciones en el nuevo lanzamiento en sí mismo; esa es la forma más fácil de hacerlo y no interrumpirá el flujo de trabajo general en absoluto.

En caso de que no funcione, debe introducir una solución rápidamente y no puede esperar a que esté lista la nueva versión, entonces prepárese para un procedimiento de Git algo complejo:

  1. Cree una rama, la llamaremos "nueva versión", pero su equipo puede adoptar cualquier convención de nomenclatura aquí, con la misma confirmación que la sugerencia actual de main. Empuje nuevo lanzamiento.
  2. Elimine y vuelva a crear la rama principal local en la confirmación de la etiqueta que creó anteriormente para la versión activa actual. Forzar empuje principal.
  3. Introduzca las correcciones necesarias en main, implemente en el entorno de prueba y pruebe. Cuando esté listo, impleméntelo en producción.
  4. Propague los cambios de la versión principal actual a la nueva versión, ya sea mediante una selección selectiva o un parche.
  5. Después de eso, rehaga el procedimiento de lanzamiento: etiquete la punta de la principal actual y presione la etiqueta, elimine y vuelva a crear la principal local en la punta de la rama de nueva versión y fuerce la inserción de la principal.
    1. Es probable que no necesite la etiqueta anterior, por lo que puede eliminarla.
    2. La rama de nueva versión ahora es redundante, por lo que también puede eliminarla.
  6. Ahora debería estar listo para continuar como de costumbre con una nueva versión. Termine propagando las revisiones de emergencia de main para desarrollar a través de una selección selectiva o un parche.

Gráficos de confirmación de Git a medida que cambian al realizar una revisión durante una versión activa en el flujo de Git mejorado. El gráfico de inicio se ha desarrollado como la línea más larga de confirmaciones, con dos confirmaciones principales divergentes antes en una confirmación, y tres confirmaciones antes de eso, la rama a diverge en una confirmación, etiquetada como v2020-09-18. Después de los primeros dos pasos anteriores, el gráfico tiene una nueva versión donde solía estar main, y main ni siquiera está con v2020-09-18. Luego se realizan el resto de los pasos, lo que da como resultado el gráfico final, donde main es una confirmación antes de donde había estado new-release, y v2020-09-20 es una confirmación antes de donde había estado v2020-09-18.

Con una planificación adecuada, una calidad de código lo suficientemente alta y un desarrollo saludable y una cultura de control de calidad, es poco probable que su equipo tenga que usar este método. Fue prudente desarrollar y probar un plan de desastres para mejorar el flujo de Git, por si acaso, pero nunca necesité usarlo en la práctica.

Configuración de CI/CD sobre flujo de Git mejorado

No todos los proyectos requieren un entorno de desarrollo dedicado. Puede ser bastante fácil configurar un entorno de desarrollo local sofisticado en cada máquina desarrolladora.

Pero un entorno de desarrollo dedicado puede contribuir a una cultura de desarrollo más sana. La ejecución de pruebas, la medición de la cobertura de las pruebas y el cálculo de métricas de complejidad en la rama de desarrollo a menudo disminuye el costo de los errores al detectarlos mucho antes de que terminen en la etapa de preparación.

Descubrí que algunos patrones de CI/CD son especialmente útiles cuando se combinan con el flujo mejorado de Git:

  • Si necesita un entorno de desarrollo, configure CI para compilarlo, probarlo e implementarlo en cada compromiso con la rama de desarrollo. Ajuste las pruebas E2E aquí también, si las tiene y si tiene sentido en su caso.
  • Configure CI para compilar, probar e implementar en el entorno de ensayo en cada confirmación en la rama principal. Las pruebas E2E también son bastante beneficiosas en este punto.
    • Puede parecer redundante usar pruebas E2E en ambos lugares, pero recuerde que las revisiones no se realizarán durante el desarrollo. La activación de E2E en las confirmaciones para main probará las revisiones y los cambios diarios antes de que salgan, pero también la activación de las confirmaciones para desarrollar detectará los errores antes.
  • Configure CI de una manera que le permita a su equipo implementar compilaciones desde el entorno principal al de producción a pedido manual.

La configuración recomendada de CI/CD con flujo de Git mejorado cuando hay un entorno de desarrollo ("dev") además de ensayo ("stage") y producción ("prod"). Todas las confirmaciones en la rama de desarrollo dan como resultado una compilación que se implementa en dev. Del mismo modo, todos los compromisos con el principal dan como resultado una compilación que se implementa en el escenario. Una solicitud manual de una confirmación específica de los resultados principales en una compilación que se implementa en producción.

Dichos patrones son relativamente simples, pero proporcionan una poderosa maquinaria para respaldar las operaciones de desarrollo del día a día.

El modelo mejorado de Git Flow: mejoras y posibles limitaciones

El flujo de Git mejorado no es para todos. Aprovecha la controvertida táctica de la fuerza empujando la rama principal, por lo que los puristas pueden resentirse. Sin embargo, desde un punto de vista práctico, no tiene nada de malo.

Como se mencionó, las revisiones son más desafiantes durante un lanzamiento, pero aún son posibles. Con la debida atención al control de calidad, la cobertura de pruebas, etc., eso no debería suceder con demasiada frecuencia, por lo que, desde mi perspectiva, es una compensación válida para los beneficios generales del flujo de Git mejorado en comparación con el flujo de Git clásico. Me interesaría mucho saber cómo funciona el flujo de Git mejorado en equipos más grandes y con proyectos más complejos, donde las revisiones pueden ocurrir con más frecuencia.

Mi experiencia positiva con el modelo de flujo mejorado de Git también gira principalmente en torno a proyectos comerciales de código cerrado. Puede ser problemático para un proyecto de código abierto donde las solicitudes de extracción a menudo se basan en una derivación de versión anterior del árbol de código fuente. No hay obstáculos técnicos para resolver eso, simplemente podría requerir más esfuerzo del esperado. Agradezco los comentarios de los lectores con mucha experiencia en el espacio de código abierto con respecto a la aplicabilidad del flujo de Git mejorado en tales casos.

Un agradecimiento especial al colega de Toptal Antoine Pham por su papel clave en el desarrollo de la idea detrás del flujo de Git mejorado.


Lecturas adicionales en el blog de ingeniería de Toptal:

  • Desarrollo basado en troncos frente a Git Flow
  • Flujos de trabajo de Git para profesionales: una buena guía de Git

Insignia de Microsoft Gold Partner.

Como Microsoft Gold Partner, Toptal es su red élite de expertos de Microsoft. Cree equipos de alto rendimiento con los expertos que necesita, ¡en cualquier lugar y exactamente cuando los necesite!