Aunque pueda parecer que gitflow es la única opción disponible, la realidad es que existen otras alternativas que puede valer la pena examinar antes de definir el flujo de trabajo a adoptar.

Y una revisión crítica de las alternativas disponibles puede no sólo ayudarnos a elegir el método que más adecuado resulte en cada caso, sino también a descubrir la causa de los problemas que estemos experimentando con nuestro workflow actual. git workflow.

Un vistazo alrededor

Aunque la primera pregunta parezca evidente una vez se ha enunciado, lo cierto es que es probable que nunca hayamos pensado en ella: ¿qué tipo de workflow se usa para el desarrollo del propio git? La respuesta, a la que denominaremos gitworkflow, la tenemos al alcance de la mano, con sólo ejecutar `git –help workflows´. Aparte de estar poco publicitado, es un tanto oscuro. Y, como no podía ser de otra manera, es escrupulosamente respetuoso en lo relativo a la historia.

En principio trabaja con cuatro ramas principales: maint (mantenimiento de release actual), master (cambios consolidados para la siguiente release), next (rama de integración con candidatos a acabar en master) y pu (un next de usar y tirar). Cada una de ellas es descendiente de la anterior, y las nuevas features entran en pu o next y se van graduando (en el sentido académico) según se va comprobando su estabilidad.

Y la forma en que se hace explícita esta graduación es mezclando la topic branch original en su nuevo grado. Es decir, habitualmente una misma rama será mezclada en múltiples destinos, garantizándose que no se introduce código ajeno haciendo que las topic branches se creen a partir del antepasado más viejo en el que se espera haya que mezclarla. Es decir, las nuevas features se crean a partir de master, pero si se trata de un hotfix, su rama se creará a partir de maint, que tiene la versión actual, y será mezclada también sobre master, next y pu.

El siguiente en la lista es evidentemente gitflow, que prácticamente todo el mundo conoce por ser probablemente el más utilizado.

Es probablemente el único que proporciona una descripción exhaustiva, a pesar de estar contenido en una entrada no especialmente extensa en un blog. Entre sus principales características se puede incluir la existencia de dos ramas principales (master y develop), mientras que las restantes deben tener una vida limitada. El flujo describe el método de trabajo y la nomenclatura para diferentes eventos habituales en el ciclo de desarrollo (las llamadas ramas de soporte: feature, hotfix, release). Uno de sus problemas es que se conoce por transmisión oral y no tanto de forma directa (no dice que master sea producción, sino sólo production-ready), pero el principal es que ya tiene una cierta edad (develop es para «automatic nightly builds»).

El siguiente en la lista es github flow. Representa probablemente el flujo más sencillo posible, y es a la vez la expresión más descarnada del continuos deployment (master “can and should deploy immediately”).

Evidentemente trabaja únicamente con master, de donde salen las topic branches, que se mezclan con el conocido mecanismo de las pull requests; y eventos como un hotfix no tienen ningún tipo de tratamiento especial aparte de la urgencia. Su principal dificultad no está en el propio workflow, sino en el esfuerzo necesario para implementar tests suficientemente confiables y que puedan ser ejecutados con cada pull request.

Un enfoque alternativo lo encontramos en el ENV branching, cuyo nombre lo describe casi por completo.

Define tres ramas (master, stage, dev) que marcan el código que está desplegado en cada entorno. Como los restantes, trabaja con topic branches, que salen todas de master (producción), y que se mezclan directamente en las ramas de cada entorno (de forma similar a la “graduación” de gitworkflow). Es un método bastante sencillo y visual, pero, como los propios autores apuntan, pierde su simplicidad cuando hay que trabajar con releases, puesto que está diseñado para websites servidos por una única aplicación, y en general necesitará de mecanismos adicionales para garantizar la consistencia entre ramas.

El trunk based development es otra metodología que parece haber atraído cierto interés recientemente, a pesar de no ser especialmente nuevo.

Como su nombre apunta, todo el trabajo se lleva a cabo en una rama principal, a partir de la que se crean, cuando es necesario, ramas release que desaparecerán cuando su período de soporte termine. No se contempla la mezcla entre master/trunk y las ramas release, sino que se utiliza cherry-pick en caso de ser necesario transportar cambios entre ellas. Hace algunas recomendaciones cuando menos discutibles, como incluir características a medio hacer desactivadas con flags de configuración. Pero su elemento menos óptimo es que es un flujo de trabajo genérico y no específico para git, por lo que es intencionalmente difuso en cómo llegan los cambios a la rama master/trunk, a pesar de utilizar conceptos como commit y pull request.

También podemos encontrar otras propuestas en cierto sentido basadas en gitflow, y que pretenden solucionar sus problemas.

Y algunos de ellos son realmente identificados por estas alternativas, que incluso proponen alguna solución válida. Oneflow es probablemente el primero en plantear la duplicidad innecesaria entre master y develop, y el “cactus model” mantiene las ramas release independientes de master. Siendo ambas propuestas más modernas que gitflow, aciertan en proponer una única rama principal donde integrar los cambios, pero el grueso de sus argumentos se concentra en la crítica de los merges “inútiles”, proponiendo rebases constantes para mantener la apariencia de una historia lineal.

Estas prácticas no sólo revelan una mala comprensión del funcionamiento y capacidades de git, sino que en realidad parecen un intento de negar la propia existencia de ramas. De hecho, el grafo que se genera con estos métodos es mucho más próximo al que obtendríamos con subversion, como de hecho sucede con algunas de sus recomendaciones («by continuously updating their working copies»).

And the winner is …

Evidentemente ninguno, aunque ni los gitflow mejorados ni trunk based development parecen ser los mejores, puesto que no explotan las fortalezas de git.

No hace falta un análisis exhaustivo para ver que github flow se adapta muy bien a productos web puros, mientras que gitflow parece mejor para software que es entregado (la release) hacia fuera del equipo que lo desarrolla. De hecho no hace falta ningún análisis, y basta leer las conclusiones del artículo original sobre github flow.

Pero siempre conviene darles una pensada en lugar de aplicarlos ciegamente.

Incluso en web-only, es probable que lo de desplegar justo después de hacer el merge nos parezca un tanto arriesgado. Podemos evitarlo manteniendo un segundo puntero que sería lo que realmente tenemos en producción, que sería un commit que también está en master, pero un cierto número de merges por detrás del HEAD. Este esquema, tal vez con algunas pinceladas adicionales, puede ser extendido fácilmente para lograr algo bastante parecido a ENV branching.

En cuanto a gitflow, no hay ninguna modificación sencilla aparente, pero la situación cambia si examinamos sus hipótesis de partida. Si no contempla la compilación continua, mucho menos la integración continua, y esa es la “causa raíz” de la dicotomía entre master y develop. Pero en un entorno donde con cada push se realiza la compilación y se pasan cuando menos las pruebas unitarias, la cosa cambia bastante. Y cualquier cosa parecida a una integración continua minimiza por definición el riesgo de problemas serios en la integración, que es la razón de ser de la rama develop.

Si bien gitflow y github flow mantienen sus dominios de aplicación, estas consideraciones desdibujan unas diferencias que parecían insalvables, y mezclar unos flujos con otros ya no parece tan complicado. Empieza a parecer menos importante qué flujo elegimos y más lo que podemos hacer con él.

Consideraciones para crear un workflow propio

Alguien, idealmente una mayoría de los involucrados, ha decidido que se use git. Sea cual sea el motivo de esta decisión, el primer consejo debería ser evidente: flow with git. Git es un sistema de control de versiones basado en historia, luego cualquier tipo de reescritura de la historia (squash o rebase) no debería ser la práctica por defecto. Y teniendo en cuenta que el manejo de ramas es uno de los puntos fuertes de git (el primero según Wikipedia), parece poco inteligente no utilizarlo. Pero tampoco parece inteligente el no usar otros mecanismos disponibles cuando el merge no es conveniente, por lo que deberíamos utilizar cherry-pick o rebase cuando debamos transportar cambios entre ramas independientes.

Trabajar con una sola rama principal es una buena práctica. Y aunque podemos apartarnos de ella, debemos tener claros los motivos por los que necesitamos hacerlo. El tener que hacer mantenimiento de múltiples versiones será un caso típico, siendo posiblemente el caso más notable el proceso de graduación que utiliza gitworkflow. Y nunca debemos olvidar que tan importante como dónde y cómo mezclamos una rama es el cuándo y dónde debemos crearla, pues la historia completa de la rama es relevante.

Nunca debemos olvidar que, sea cual sea nuestro flujo de git, su función principal es apoyar nuestro flujo de release.

Tanto si nos preocupa como si no el saber qué commit está desplegado en producción, en general a partir de ese commit generamos un artefacto (probablemente binario), al que posiblemente hayamos asociado un número de versión y que habremos subido a algún tipo de repositorio. Y ese artefacto será lo que iremos desplegando sucesivamente a lo largo de varios entornos. La función de nuestro flujo de trabajo con git es ante todo facilitar esa tarea, y este será el motivo que nos hará descartar gitworkflow si su mecanismo de graduación no se adapta bien a nuestros procesos de release.

Si utilizamos alguna herramienta para llevar el control de nuestros cambios, es muy conveniente incorporarla de alguna manera a nuestro workflow, usando los identificadores en el nombre de las ramas. Esta forma de proceder nos responde de forma natural a la pregunta sobre el punto donde deben crearse las ramas: si se trata por ejemplo de una subtarea saldrá de la rama de su tarea madre. Y no debemos quedarnos ahí, sino que debemos profundizar la conexión entre ambos, por ejemplo cambiando automáticamente de estado cuando la rama se mezcla o se borra.

Aunque no lo parezca, este tipo de acciones externas también forman parte de nuestro flujo de git.

No solo nuestros procesos de release y desarrollo deben ser tenidos en cuenta al establecer nuestro flujo de trabajo, sino también las herramientas que utilicemos para ello. Por ejemplo, la utilización de maven nos conduce de forma natural a un flujo donde en la rama master solo hay versiones snapshot, creando ramas cuando llega el momento de generar un artefacto de tipo release. O alternativamente podemos utilizar git para determinar la versión de artefacto que generamos mediante el conteo de commits a partir del tag que marca el comienzo de la release actual.

Finalmente, una vez definido nuestro flujo de trabajo, debemos prestar atención a las desviaciones del mismo.

Habrá ocasiones en que nos debamos apartar del camino habitual, y tendremos que encontrar mecanismos para gestionar las excepciones, asegurándonos de que siguen siendo eventos excepcionales.

Y también debemos prestar atención a las desviaciones menores y sus causas, que pueden servirnos para detectar problemas originados en fases anteriores, como la necesidad de mezclar unas topic branch en otras, lo que puede indicar que historias de usuario que se creían independientes en realidad no lo son tanto.