Usando Git Flow

Como vimos en el artículo anterior (que es recomendable leer para entender correctamente este), podemos hacer uso de flujos de trabajo bastante completos y potentes con Git.

El problema es que la necesidad de ejecutar una serie de comandos cada vez que se quiere hacer una tarea cotidiana (integrar una rama de feature, lanzar una versión, ...) es muy dada a errores, ya que con que nos equivoquemos en alguno de ellos, o los ejecutemos con algún parámetro inadecuado nuestro repositorio puede dejar de funcionar tal como lo queremos.

Por ello, el creador del flujo de trabajo visto en el anterior artículo creo una herramienta llamada git-flow, que añade una capa por encima de git, facilitando todas las tareas descritas anteriormente.

Instalando git-flow

Esto es dependiente de la plataforma que estemos usando. Por ejemplo, si usamos Linux bastará con ejecutar el siguiente comando, que descargará un script de instalación y lo ejecutará:

$ wget --no-check-certificate -q -O - https://github.com/nvie/gitflow/raw/develop/contrib/gitflow-installer.sh | sudo bash

En el caso de usar MacOS, podemos instalarlo gracias a homebrew:

$ brew install git-flow

Una vez instalado no necesitaremos nada más, y podremos empezar a usarlo en nuestros nuevos repositorios (o en los ya existentes).

Usando git-flow

Como hemos dicho anteriormente git-flow no es más que una capa que se pone por encima de git, por lo que para usarlo, pasaremos el parámetro flow al comando git y a continuación indicaremos la operación que queremos realizar.

Si queremos ver todas las opciones posibles solo tenemos que pasar como parámetro help.

$ git flow help
usage: git flow <subcommand>

Available subcommands are:
   init      Initialize a new git repo.
   feature   Manage your feature branches.
   release   Manage your release branches.
   hotfix    Manage your hotfix branches.
   support   Manage your support branches.
   version   Shows version information.

Como podemos ver, la forma de usarlo es bastante sencilla, pero vamos a verla más en detalle.

Inicializando el repositorio

La primera orden que debemos realizar con git-flow antes de comenzar a usar el resto de las opciones es inicializar el repositorio. Este comando creará la rama develop, y configurará el prefijo que queremos que tengan los distintos tipos de ramas temáticas.

$ git flow init

Tras una serie de preguntas (que podemos dejar vacías para que se usen las opciones predeterminadas), nuestro repositorio quedará inicializado correctamente, y nos dejará puesto como HEAD la rama develop.

Desarrollando en ramas

Si llegado cierto punto queremos crear una rama de features para desarrollar cierta característica en particular, no tendremos más que crear la rama.

$ git flow feature start bug-144

Con el anterior comando git-flow creará una rama llamada feature/bug-144 y posicionará nuestro HEAD en ella. A continuación, no tendremos más que desarrollar y realizar commits de forma normal y corriente.

Llegado el momento de integrar el trabajo (con la rama develop, recordad) bastará con ejecutar el siguiente comando:

$ git flow feature finish bug-144

De esta manera, git-flow se encargará de integrar la rama en develop, borrar la rama bug-144 y por último situar nuestro HEAD de nuevo en la rama develop.

Este es el flujo normal con este tipo de ramas, pero como dijimos en el artículo anterior, podríamos querer que nuestra rama bug-144 fuese subida al repositorio central para que varios desarrolladores pudiesen trabajar en ella. Para ello, bastará con pasarle como parámetro la opción publish.

$ git flow feature publish bug-144

De esta manera se subirá a origin la rama bug-144. Para que cualquier otro desarrollador pueda bajarse esa rama habrá que hacer algo análogo, usando la opción track.

$ git flow feature track bug-144

Llegado a este punto ambos desarrolladores podrán realizar cambios y commits sobre la misma rama de desarrollo. Ya solo queda poder bajarse de forma regular los cambios subidos por la otra persona. Tarea que se realizará mediante un pull.

$ git flow feature pull origin bug-144

Mediante este último comando le indicamos a git-flow que se descargue todos los cambios realizados en el repositorio origin para la rama bug-144. Este último parámetro no es obligatorio, bajándose los cambios de todas las ramas en caso de no ponerlo.

Por último, y si vemos la ayuda (git flow feature help), podremos ver como existen un par de opciones más que poder realizar, pero debido a su sencillez, se dejan a la curiosidad del lector :)

¿Y el resto de ramas?

Pues el resto de ramas (las de releases y hotfix) funcionan de forma totalmente análoga a lo explicado en el anterior apartado.

$ git flow hotfix start
$ git flow release finish v2.2
...

Ambos tienen los mismos subcomandos (alguno menos en realidad), pero los principales (start, finish y publish) funcionan de igual manera a lo explicado anteriormente.

Concluyendo...

Y en conclusión no hay mucho más que contar. En esta serie de dos artículos hemos podido ver la potencia que tiene Git, la libertad que nos brinda a la hora de elegir como trabajar, y el jugo que le podemos sacar usándolo correctamente.

La mayor ventaja de git-flow sobre otros modelos de trabajo es que nos obliga a ser organizados en la forma en la que tratamos nuestro repositorio, creando un flujo de trabajo que se adapta muy bien a la mayor parte de los desarrollos.

Y esto es todo,

$ git push sysvar usando-git-flow

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 ;)