Articles

Un exitoso modelo de ramificación de Git

Posted on

Nota de reflexión (5 de marzo de 2020)

Este modelo fue concebido en 2010, hace ya más de 10 años, y no mucho después de que el propio Git naciera. En esos 10 años, git-flow (el modelo de bifurcación expuesto en este artículo) se ha hecho enormemente popular en muchos equipos de software, hasta el punto de que la gente ha empezado a tratarlo como una especie de estándar, pero desgraciadamente también como un dogma o una panacea.

Durante esos 10 años, el propio Git ha tomado el mundo por asalto, y el tipo de software más popular que se está desarrollando con Git se está desplazando hacia las aplicaciones web, al menos en mi burbuja de filtros. Las aplicaciones web son típicamente entregadas de forma continua, sin retroceso, y no tienes que soportar múltiples versiones del software que se ejecuta en la naturaleza.

Esta no es la clase de software que tenía en mente cuando escribí el blogpost hace 10 años. Si su equipo está haciendo la entrega continua de software, yo sugeriría adoptar un flujo de trabajo mucho más simple (como GitHubflow) en lugar de tratar de meter a git-flow en su equipo.

Si, sin embargo, usted está construyendo software que está explícitamente versionado, o si necesita soportar múltiples versiones de su software en la naturaleza, el git-flow todavía puede ser tan bueno para su equipo como lo ha sido para la gente en los últimos 10 años. En ese caso, siga leyendo.

Para terminar, recuerde siempre que las panaceas no existen. Ten en cuenta tu propiocontexto. No seas odioso. Decide por ti mismo.

En este post presento el modelo de desarrollo que he introducido para algunos de mis proyectos (tanto en el trabajo como en el ámbito privado) hace aproximadamente un año, y que ha resultado ser muy exitoso. Llevo tiempo queriendo escribir sobre él, pero nunca he encontrado el momento de hacerlo a fondo, hasta ahora. No voy a hablar de los detalles del proyecto, simplemente de la estrategia de ramificación y gestión de liberaciones.

¿Por qué git?

Para una discusión a fondo sobre los pros y los contras de Git en comparación con los sistemas de control de código fuente centralizados, consulte la web. Hay muchas guerras de fuego allí. Como desarrollador, prefiero Git por encima de todas las demás herramientas actuales. Desde el mundo clásico de CVS/Subversion del que vengo, fusionar/ramificar siempre se ha considerado un poco aterrador («¡cuidado con los conflictos de fusión, te muerden!») y algo que sólo se hace de vez en cuando.

Pero con Git, estas acciones son extremadamente baratas y sencillas, y se consideran una de las partes fundamentales de tu flujo de trabajo diario, realmente. Por ejemplo, en los libros de CVS/Subversion, el branching y el merging se tratan por primera vez en los últimos capítulos (para usuarios avanzados), mientras que en todos los libros de Git, ya se tratan en el capítulo 3 (básico).

Como consecuencia de su simplicidad y naturaleza repetitiva, el branching y el merging ya no son algo a lo que temer. Se supone que las herramientas de control de versiones ayudan a la ramificación/fusión más que cualquier otra cosa.

Basta de hablar de las herramientas, pasemos al modelo de desarrollo. El modelo que voy a presentar aquí no es más que un conjunto de procedimientos que cada miembro del equipo tiene que seguir con el fin de llegar a un proceso de desarrollo de software gestionado.

Descentralizado pero centralizado¶

La configuración del repositorio que utilizamos y que funciona bien con este modelo de ramificación, es la de un repo central «de verdad». Ten en cuenta que este repo sólo se considera el central (ya que Git es un DVCS, no existe un repo central a nivel técnico). Nos referiremos a este repo como origin, ya que este nombre es familiar para todos los usuarios de Git.

Cada desarrollador tira y empuja al origen. Pero además de las relaciones centralizadaspush-pull, cada desarrollador puede también tirar de los cambios de otros peerspara formar sub equipos. Por ejemplo, esto podría ser útil para trabajar junto con dos o más desarrolladores en una nueva característica grande, antes de empujar el trabajo en curso aorigin prematuramente. En la figura anterior, hay subequipos de Alice y Bob, Alice y David, y Clair y David.

Técnicamente, esto no significa más que Alice ha definido un Git remoto, llamado bob, apuntando al repositorio de Bob, y viceversa.

Las ramas principales ¶

En el núcleo, el modelo de desarrollo está muy inspirado en los modelos existentes por ahí. El repo central alberga dos ramas principales con un tiempo de vida infinito:

  • master
  • develop
  • La rama master en origin debería ser familiar para todo usuario de Git. Paralelamente a la rama master, existe otra rama llamada develop.

    Consideramos que origin/master es la rama principal donde el código fuente deHEAD siempre refleja un estado listo para producción.

    Consideramos que origin/develop es la rama principal donde el código fuente deHEAD siempre refleja un estado con los últimos cambios de desarrollo entregados para la siguiente versión. Algunos llamarían a esto la «rama de integración». Aquí es donde se construyen las construcciones nocturnas automáticas.

    Cuando el código fuente en la rama develop alcanza un punto estable y está listo para ser liberado, todos los cambios deben ser fusionados de nuevo en masterde alguna manera y luego etiquetados con un número de versión. Cómo se hace esto en detalle se discutirá más adelante.

    Por lo tanto, cada vez que los cambios se fusionan de nuevo en master, se trata de una nueva versión de producción por definición. Tendemos a ser muy estrictos en esto, por lo que teóricamente, podríamos utilizar un script de gancho Git para construir automáticamente y desplegar nuestro software a nuestros servidores de producción cada vez que había un commit enmaster.

    Bifurcaciones de soporte ¶

    Además de las ramas principales master y develop, nuestro modelo de desarrollo utiliza una variedad de ramas de soporte para ayudar al desarrollo paralelo entre los miembros del equipo, facilitar el seguimiento de las características, preparar los lanzamientos de producción y ayudar a solucionar rápidamente los problemas de producción en vivo. A diferencia de las ramas principales, estas ramas siempre tienen un tiempo de vida limitado, ya que serán eliminadas eventualmente.

    Los diferentes tipos de ramas que podemos utilizar son:

    • Bramas de características
    • Bramas de liberación
    • Bramas de corrección
    • Cada una de estas ramas tiene un propósito específico y está sujeta a reglas estrictas en cuanto a qué ramas pueden ser su rama de origen y qué ramas deben ser sus objetivos de fusión. Vamos a caminar a través de ellos en un minuto.

      De ninguna manera son estas ramas «especial» desde una perspectiva técnica. Los tipos de ramas se clasifican por la forma en que las usamos. Son, por supuesto, ramas deGit de toda la vida.

      Ramificaciones de características ¶

      Puede bifurcarse desde: develop Debe fusionarse de nuevo en: develop Convención de nomenclatura de la rama: cualquier cosa excepto masterdeveloprelease-*, o hotfix-*

      Las ramas de características (o a veces llamadas ramas temáticas) se utilizan para desarrollar nuevas características para la próxima o una futura versión lejana. Cuando se inicia el desarrollo de una característica, la versión objetivo en la que se incorporará esta característica puede ser desconocida en ese momento. La esencia de una rama de característica es que existe mientras la característica está en desarrollo, pero eventualmente se fusionará de nuevo en develop (para añadir definitivamente la nueva característica a la próxima versión) o se descartará (en caso de un experimento decepcionante).

      Las ramas de características suelen existir sólo en los repositorios de desarrolladores, no en origin.

      Crear una rama de características ¶

      Cuando se empieza a trabajar en una nueva característica, se ramifica desde la rama develop.

      $ git checkout -b myfeature developSwitched to a new branch "myfeature"

      Incorporar una característica terminada en develop

      Las características terminadas pueden fusionarse en la rama develop para añadirlas definitivamente a la próxima versión:

      $ git checkout developSwitched to branch 'develop'$ git merge --no-ff myfeatureUpdating ea1b82a..05e9557(Summary of changes)$ git branch -d myfeatureDeleted branch myfeature (was 05e9557).$ git push origin develop

      La bandera --no-ff hace que la fusión siempre cree un nuevo objeto de confirmación, incluso si la fusión se puede realizar con un avance rápido. Esto evita la pérdida de información sobre la existencia histórica de una rama de la característica y agrupa todos los commits que juntos añadieron la característica. Compara:

      En el último caso, es imposible ver en el historial de Git cuáles de los objetos de commit juntos han implementado una característica-tendrías que leer manualmente todos los mensajes de registro. Revertir una característica completa (es decir, un grupo de commits), es un verdadero dolor de cabeza en la última situación, mientras que se hace fácilmente si se utiliza la bandera --no-ff.

      Sí, se crearán unos cuantos objetos de commit más (vacíos), pero la ganancia es mucho mayor que el coste.

      Ramificaciones de liberación ¶

      Puede bifurcarse desde: develop Debe fusionarse de nuevo en: develop y master Convención de nomenclatura de ramas: release-*

      Las ramas de lanzamiento apoyan la preparación de una nueva versión de producción. Permiten poner los puntos sobre las íes a última hora. Además, permiten realizar correcciones de errores menores y preparar los metadatos de una versión (número de versión, fechas de compilación, etc.). Al hacer todo este trabajo en una rama de lanzamiento, la rama develop queda preparada para recibir las características de la próxima gran versión.

      El momento clave para bifurcar una nueva rama de lanzamiento desde develop es cuando develop refleja el estado deseado de la nueva versión. Al menos todas las características que están destinadas a la versión que se va a construir deben ser fusionadas en develop en este momento. Todas las características orientadas a futuras versiones no pueden – deben esperar hasta después de que la rama de lanzamiento se ramifique.

      Es exactamente al comienzo de una rama de lanzamiento que la próxima versión se asigna un número de versión – no antes. Hasta ese momento, la rama develop refleja los cambios para la «próxima versión», pero no está claro si esa «próxima versión» se convertirá finalmente en la 0.3 o la 1.0, hasta que se inicie la rama de lanzamiento. Esa decisión se toma en el inicio de la rama de liberación y es llevada a cabo por las reglas del proyecto sobre el salto de número de versión.

      Creando una rama de liberación ¶

      Las ramas de liberación se crean a partir de la rama develop. Por ejemplo, digamos que la versión 1.1.5 es la versión de producción actual y que tenemos una gran liberación en camino. El estado de develop está listo para la «próxima versión» y hemos decidido que ésta será la versión 1.2 (en lugar de la 1.1.6 o la 2.0). Así que se quita la rama y se da a la rama de lanzamiento un nombre que refleje el nuevo número de versión:

      $ git checkout -b release-1.2 developSwitched to a new branch "release-1.2"$ ./bump-version.sh 1.2Files modified successfully, version bumped to 1.2.$ git commit -a -m "Bumped version number to 1.2" Bumped version number to 1.21 files changed, 1 insertions(+), 1 deletions(-)

      Después de crear una nueva rama y cambiar a ella, aumentamos el número de versión.Aquí, bump-version.sh es un script de shell ficticio que cambia algunos archivos en la copia de trabajo para reflejar la nueva versión. (Esto puede ser, por supuesto, un cambio manual -el punto es que algunos archivos cambian). A continuación, el número de la versión modificada se confirma.

      Esta nueva rama puede existir durante un tiempo, hasta que la versión se lance definitivamente. Durante ese tiempo, las correcciones de errores se pueden aplicar en esta rama (en lugar de en la rama develop). Está estrictamente prohibido añadir nuevas características aquí. Deben fusionarse en develop, y por lo tanto, esperar a la próxima gran versión.

      Finalización de una rama de liberación ¶

      Cuando el estado de la rama de liberación está listo para convertirse en una verdadera liberación, algunas acciones deben llevarse a cabo. Primero, la rama de lanzamiento se fusiona en master (ya que cada commit en master es una nueva versión por definición, recuerda). Luego, ese commit en master debe ser etiquetado para facilitar la referencia futura a esta versión histórica. Por último, los cambios realizados en la rama de liberación deben ser fusionados de nuevo en develop, para que las futuras versiones también contengan estas correcciones de errores.

      Los dos primeros pasos en Git:

      $ git checkout masterSwitched to branch 'master'$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)$ git tag -a 1.2

      La liberación ya está hecha, y etiquetada para futuras referencias.

      Edición: también podrías querer usar las banderas -s o -u <key> para firmar tu etiqueta criptográficamente.

      Para mantener los cambios realizados en la rama de lanzamiento, tenemos que fusionar esos de nuevo en develop, sin embargo. En Git:

      $ git checkout developSwitched to branch 'develop'$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)

      Este paso puede provocar un conflicto de fusión (probablemente incluso, ya que hemos cambiado el número de versión). Si es así, arréglalo y haz un commit.

      Ahora sí que hemos terminado y la rama release puede ser eliminada, ya que no la necesitamos:

      $ git branch -d release-1.2Deleted branch release-1.2 (was ff452fe).

      Bifurcaciones Hotfix ¶

      Puede bifurcarse desde: master Debe fusionarse de nuevo en: develop y master Convención de nomenclatura de ramas: hotfix-*

      Las ramas de corrección son muy parecidas a las ramas de lanzamiento en el sentido de que también están destinadas a preparar una nueva versión de producción, aunque no planificada. Surgen de la necesidad de actuar inmediatamente sobre un estado no deseado de una versión de producción en vivo. Cuando un error crítico en una versión de producción debe ser resuelto inmediatamente, una rama de hotfix puede ser ramificada desde la etiqueta correspondiente en la rama maestra que marca la versión de producción.

      La esencia es que el trabajo de los miembros del equipo (en la rama develop) continúe, mientras que otra persona está preparando una solución rápida de producción.

      Creación de la rama de hotfix ¶

      Las ramas de hotfix se crean a partir de la rama master. Por ejemplo, digamos que la versión 1.2 es la actual versión de producción que se está ejecutando en vivo y está causando problemas debido a un grave error. Pero los cambios en develop son todavía inestables. Podemos entonces bifurcarnos de una rama de hotfix y empezar a arreglar el problema:

      $ git checkout -b hotfix-1.2.1 masterSwitched to a new branch "hotfix-1.2.1"$ ./bump-version.sh 1.2.1Files modified successfully, version bumped to 1.2.1.$ git commit -a -m "Bumped version number to 1.2.1" Bumped version number to 1.2.11 files changed, 1 insertions(+), 1 deletions(-)

      ¡No olvides aumentar el número de versión después de bifurcarte!

      Entonces, arregla el error y confirma la solución en uno o más commits separados.

      $ git commit -m "Fixed severe production problem" Fixed severe production problem5 files changed, 32 insertions(+), 17 deletions(-)

      Terminando una rama de hotfix ¶

      Cuando haya terminado, el bugfix necesita ser fusionado de nuevo en master, pero también debe volver a fusionarse en develop, para garantizar que la corrección de errores se incluya también en la siguiente versión. Esto es completamente similar a cómo se terminan las ramas de liberación.

      Primero, actualice master y etiquete la liberación.

      $ git checkout masterSwitched to branch 'master'$ git merge --no-ff hotfix-1.2.1Merge made by recursive.(Summary of changes)$ git tag -a 1.2.1

      Edita: también podrías querer usar los flags -s o -u <key> para firmar criptográficamente tu etiqueta.

      A continuación, incluye la corrección de errores en develop, también:

      $ git checkout developSwitched to branch 'develop'$ git merge --no-ff hotfix-1.2.1Merge made by recursive.(Summary of changes)

      La única excepción a la regla aquí es que, cuando existe una rama de lanzamiento, los cambios del hotfix deben fusionarse en esa rama de lanzamiento, en lugar de develop. Si se fusiona la corrección de errores con la rama de publicación, al final la corrección de errores se fusionará también con develop, cuando la rama de publicación esté terminada. (Si el trabajo en develop requiere inmediatamente esta corrección de errores y no puede esperar a que la rama de publicación esté terminada, puede fusionar con seguridad la corrección de errores en develop ahora también.)

      Por último, elimine la rama temporal:

      $ git branch -d hotfix-1.2.1Deleted branch hotfix-1.2.1 (was abbe5d6).

      Resumen ¶

      Aunque no hay nada realmente sorprendente en este modelo de ramificación, la figura del «bigpicture» con la que comenzó este post ha resultado ser tremendamente útil en nuestros proyectos. Forma un modelo mental elegante que es fácil de comprender y permite a los miembros del equipo desarrollar una comprensión compartida de los procesos de ramificación y liberación.

      Aquí se ofrece una versión en PDF de alta calidad de la figura. Adelante, cuélgala en la pared para consultarla rápidamente en cualquier momento.

      Actualización: Y para quien lo haya solicitado: aquí está elgitflow-model.src.key de la imagen del diagrama principal (Apple Keynote).


      Git-branching-model.pdf

      Otras entradas de este blog

      • Herramientas de poder de Git para el uso diario
      • Una introducción a los decodificadores
      • La deuda técnica es una deuda real
      • Bonito código
      • Bonito mapa

      .

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *