Entendiendo Git Flow

Una de las características que más me gustan de Git es que es una auténtica navaja suiza en cuanto a que permite seguir el flujo de trabajo que más nos convenga en nuestro proyecto.

La potencia que tiene la gestión de ramas, la forma en la que está diseñado y el hecho de que sea distribuido nos permite adoptar multitud de flujos; desde el más simple en el que imitamos un repositorio centralizado como subversion hasta otros más complicados en los que se crean multitud de ramas y servidores que nos ayudan a organizar y jerarquizar mejor todo.

Hoy vamos a hablar de uno de estos últimos, un modelo que ha cobrado muchísima fama últimamente y que a pesar de poder ser algo lioso al principio, veremos que nos puede ayudar muchísimo en el día a día.

Este artículo está dividido en dos partes (enlace a la segunda parte), siendo esta primera la encargada de explicar el flujo de trabajo a seguir y la segunda la encargada de explicar como usar git-flow, una herramienta creada para facilitar el uso de este modelo (se instala como un plugin de git).

Entendiendo este artículo

Para leer este artículo hay que tener una base (tampoco demasiado amplia) sobre gestores de versiones distribuidos (Git, Bazar, Mercurial, ...) y sobre terminología Git.

Como repaso rápido recordemos lo siguiente:

  • El término HEAD referencia al último commit existente en la rama en la que estemos.
  • Llamaremos origin al servidor central donde estará alojado nuestro proyecto.
  • Usaremos la nomenclatura estándar al nombrar ramas: servidor/nombre_rama.

Y poco más. Sabido esto podemos empezar... :)

El modelo de ramas acertado de Git

O como se llama en inglés A successful Git branching model (lo que es poco acertado es la traducción que he hecho).

Creado por Vincent Driessen, es un modelo de flujo de trabajo para Git que da muchísima importancia a las ramas y que de hecho las crea de varios tipos, de forma temática, tal que cada tipo de rama es creada con un objetivo en concreto.

A continuación vamos a ir explicando cada uno de estos tipos, con el uso y el objetivo que tienen en este flujo de trabajo.

La rama master

La rama master es la única rama existente que nos proporciona Git al crear un repositorio nuevo.

Esta rama (sincronizada en todo momento con origin/master) tiene como objetivo ser el contenido del servidor de producción. Es decir, el HEAD de esta rama ha de apuntar en todo momento a la última versión de nuestro proyecto.

../../static/images/0007-entendiendo-git-flow/master.png

Como podemos ver, cada commit (ilustrado por las pelotitas) es una nueva versión del proyecto, que a su vez está referenciada mediante un tag (representada por la nube).

No se va a desarrollar desde esta rama en ningún momento.

La rama develop

Esta rama funciona paralelamente a la master. Si la anterior contenía las versiones desplegadas en producción, esta (que también estará sincronizada con origin/develop) contendrá el último estado de nuestro proyecto. Es decir, esta rama contiene todo el desarrollo del proyecto hasta el último commit realizado.

../../static/images/0007-entendiendo-git-flow/develop.png

Cuando esta rama adquiera estabilidad y los desarrolladores quieran lanzar una nueva versión, bastará con hacer un merge a la rama master (no de forma directa, ya veremos como hacerlo) y asignarle un número de versión mediante un tag.

Esto será lo que cree una nueva versión de nuestro proyecto. Recordemos que queremos ser bastante estrictos en cuanto a que el master solo alojará commits que supongan nuevas versiones. Nada más. Para ver el estado del desarrollo usaremos la rama develop.

Las ramas de features

Que varias personas trabajen sobre la misma rama es bastante caótico ya que se aumenta el número de conflictos que se dan. A pesar de que los repositorios distribuidos faciliten esta tarea al guardar los commits solo localmente, tiene mucho más sentido usar la potencia de las ramas de Git.

Cada vez que necesitemos programar una nueva característica en nuestro proyecto crearemos una nueva rama para la tarea. De hecho, lo utópico sería que todo el desarrollo se realizase en este tipo de ramas.

Estas ramas pueden tener cualquier nombre que no empiece por release-, hostfix-, ni master o develop. El nombre deberá reflejar el propósito de la rama (ej. form_registro_usuarios, i18n, bug_1134, ...).

Ya que develop contiene la última foto de nuestro proyecto, crearemos la nueva rama a partir de aquí.

$ git checkout -b bug_1134 develop

Una vez finalizada la tarea, solo tendremos que integrar la rama creada dentro de develop. Para esto usaremos un merge normal y corriente con un parámetro extra, --no-ff.

Este flag obliga a Git ha generar un commit para el merge. Con esto se evita que Git haga un fast-forward si es posible (es uno de los métodos para realizar merges, en los que se pierde la historia de la rama).

De esta manera, en todo momento en la historia del repositorio se tendrá constancia de que hubo una rama donde se desarrolló cierta funcionalidad, que commits contenía, y cuando se integró en el trunk.

../../static/images/0007-entendiendo-git-flow/feature.png

Una vez integrada la rama en develop, podremos eliminarla y actualizar origin.

$ git checkout develop
$ git merge --no-ff bug_1134
$ git branch -d bug_1134
$ git push origin develop

Por último, estas ramas no tienen por qué estar necesariamente subidas a origin, aunque si se quiere compartir ese desarrollo entre varios programadores, puede hacerse sin problemas.

Las ramas de lanzamiento

Como hemos dicho antes, el desarrollo del proyecto ha de realizarse en la rama develop, para posteriormente lanzar una nueva versión desde la rama master.

Esto no se hará directamente con un merge desde la rama develop a master, si no que se usarán las ramas de lanzamiento para este fin.

Este tipo de ramas sirve para poder liberar cuanto antes la rama develop para continuar el desarrollo, y alojará todos aquellos commits que son de preparación para el lanzamiento de la versión: cambiar el número de la versión en los ficheros, compilar la documentación necesaria, empaquetar librerías, etc.

Es decir, todo aquello que no tiene que ver directamente con el desarrollo, si no con el lanzamiento de la siguiente versión del proyecto.

La creación de esta rama será análoga a las de otros tipos, teniendo en cuenta que tendrá que prefijarse con el nombre release-, seguido del número de versión que tendrá.

$ git checkout -b release-1.2 develop

Una vez terminada la preparación de la siguiese, habrá que realizar un merge del HEAD de la rama release con el master. De esta forma se creará una nueva versión.

Por último, se creará un tag en el commit del master para marcar la versión.

../../static/images/0007-entendiendo-git-flow/release.png

Como se puede ver en el gráfico, los cambios no sólo se integrarán en el master, si no que también lo harán en la rama develop, de esta manera no perderemos los cambios realizados en la rama release, y se integrarán en el desarrollo. Por último, y dado que ya no hace falta, podemos borrar la rama creada.

Así pues, las instrucciones para finalizar una rama de lanzamiento son las siguientes:

$ git checkout master
$ git merge --no-ff release-1.2
$ git tag -a 1.2
$ git checkout develop
$ git merge --no-ff release-1.2
$ git branch -d release-1.2

Como detalle adicional mencionar que en las ramas de lanzamiento no se puede añadir funcionalidad al producto. Es una rama cuyo objetivo es únicamente el realizar el trabajo necesario para lanzar una nueva release.

Si en este proceso se detectase un bug nuevo, se corregiría en la rama de lanzamiento. Al finalizarla, como el trabajo se integrará en la rama develop, el commit que arregló dicho fallo se incluirá igualmente.

Las ramas de bugs urgentes

Y por último tenemos las ramas de bugs urgentes. Estas ramas son muy parecidas en funcionamiento a las ramas de lanzamiento. Su propósito es arreglar algún fallo en el desarrollo que necesita de una nueva release del proyecto inmediatamente.

Esta rama se creará a partir de master ya que únicamente queremos resolver el fallo (sin incluir nada del nuevo desarrollo realizado en la rama develop). Su nombre deberá prefijarse mediante hotfix- y una vez creada la rama simplemente se corregirá el fallo desde la misma.

$ git checkout -b hotfix-1.1.2 master

Una vez corregido el fallo tendremos que integrar el arreglo en el master. Para ello, y como siempre, se hará un merge y se creará un tag para indicar que se ha creado una nueva versión del proyecto.

También se tendrá que integrar la corrección del bug en la rama develop, ya que no queremos perder el fix en el desarrollo de futuras versiones.

../../static/images/0007-entendiendo-git-flow/hotfix.png

Los comandos necesarios para llevar a cabo la finalización de la rama hotfix son los siguientes:

$ git checkout master
$ git merge --no-ff hotfix-1.1.2
$ git tag -a 1.1.2
$ git checkout develop
$ git merge --no-ff hotfix-1.1.2

Hay que tener en cuenta una pequeña excepción, el caso en el que exista una rama de lanzamiento cuando creamos la rama hotfix.

En este caso, se hará todo exactamente igual con una salvedad. Y es que a la hora de integrar los commits de la rama hotfix, por un lado lo haremos al master y por el otro, en vez de a la rama develop, lo haremos a la rama de lanzamiento.

De esta manera, el bug se verá corregido en la nueva versión que está a punto de lanzarse, y, cuando esta se integre en develop (recordad que las ramas de lanzamiento se integran en el master y en develop al finalizarse), se verá corregido en el resto del desarrollo.

Las ramas de soporte

Estas ramas sirven para ofrecer soporte a largo alcance de una versión del proyecto. Podemos tener varias versiones en producción, las 1.x y las 2.x.

En caso de que no queramos matar el desarrollo de la 1.x porque aún nos es necesario, podemos usar este tipo de ramas para facilitar el mantenimiento de ambos desarrollos en paralelo.

En cualquier caso, el soporte de estas ramas está aún en proceso muy experimental, por lo que a fecha de este artículo es mejor no usarlas.

Resumiendo...

Como podéis ver aunque este flujo de trabajo es algo rebuscado, se adapta muy bien al trabajo real que se hace en muchos desarrollos.

En el próximo artículo veremos git-flow, una herramienta creada para facilitar el seguir este flujo de trabajo. Como habréis visto, las formas de crear y finalizar las ramas son siempre prácticamente iguales, y, aparte, en el caso de la integración de los cambios, hay que pasar el flag no-ff siempre al comando. git-flow nació para evitar errores en estos aspectos y para facilitar el día a día al seguir este flujo (ejecutar un solo comando para finalizar una rama, en vez de cinco por ejemplo).

Y por último, y como chuleta para tener siempre cerca, un resumen de los tipos de ramas:

Master

Cada commit indica una nueva versión del proyecto.

  • Se crea a partir de: -
  • Se integra en: -
Develop

Contiene el desarrollo del proyecto

  • Se crea a partir de: -
  • Se integra en: -
Feature branch

Cada rama contiene el desarrollo de una funcionalidad.

  • Se crea a partir de: develop
  • Se integra en: develop
Release branch (release-XXX)

Se realiza el trabajo necesario para lanzar una nueva versión.

  • Se crea a partir de: develop
  • Se integra en: develop y master
Hotfix branch (hotfix-XXX)

Se corrige un fallo urgente.

  • Se crea a partir de: master
  • Se integra en: develop y master

Podéis ver un resumen general de todo lo contado aquí en un pdf creado por el autor de este modelo. En el apartado de referencias (a continuación de esto), tenéis igualmente el enlace al post original, donde se explica todo muchísimo mejor que aquí, pero en un castizo inglés ;)

Comentarios