Política de ramificación en git

gitlogoAl desarrollar software, y en especial al hacerlo en equipo, se hace fundamental utilizar un sistema de control de versiones. No voy a entrar en el detalle de porqué es importante, porque eso está más que cubierto en muchos sitios. Lo difícil es encontrar una manera de gestionar la utilización del repositorio y establecer unas reglas para la creación de ramas, commits, versiones y lanzamientos. Ahora que en la Universidad hemos comenzado un proyecto en el que se nos requiere la utilización de Git, y más concretamente en GitHub, nos hemos encontrado con que:

  1.  La mayoría de la gente, al menos en mi Universidad, no saben utilizar control de versiones (lo que daría para hablar un buen rato).
  2. Es muy difícil establecer unas reglas para que todo funcione bien y no liarse en exceso a la hora de utilizar Git.

Buscando por ahí se ve que, evidentemente, no hay un estándar como tal para este tipo de cosas, pero hay una serie de buenas prácticas, y una que tiene mucho éxito, y que me parece muy sensata, es esta propuesta, que aún no he encontrado bien explicada en español, así que eso voy a hacer, y ya estando en el tema más adelante, seguramente en otro post, hablaré un poco de la numeración de versiones.

La numeración de versiones es otro aspecto que, al no haber estándares y depender mucho del proyecto y de sus arquitectos y desarrolladores, acaba por ser un caos. ¿Una v1.0 es una versión final? ¿Una v1.0.1 es una versión nueva o solo un parche? ¿La v1.10 puede existir o después de la v1.9 se pasa a v2.0? ¿Utilizar x.y.z o x.y? ¿x.y.z.t? ¿Fechas en el nombre? ¿Build numbers? Hay muchísimas posibilidades, y a lo largo del tiempo con seguridad que nos encontraremos con todas ellas en un sitio y otro. Para los proyectos propios se hace necesario tomar una decisión sobre la política a seguir.

Política de Ramificación en Git

El resumen de todo lo que diré a continuación se encuentra en esta imagen, tomada del blog de Vincent Driessen, que habla de cinco ramificaciones a la hora de desarrollar, que me parecen lo más simple y sensato:

git-model

Visto así parece un poco abrumador, pero si vamos por partes vemos la lógica. En el post original está mucho más detallado, yo aquí solo quiero hacer un resumen general para poder referirme a él en el futuro.

Git es un sistema descentralizado pero centralizado a la vez. Esto significa que, aunque se trabaja con repositorios remotos, el repositorio central es tal sólo porque así lo decide el equipo. Desde el punto de vista técnico, es únicamente un repositorio más. Pero lo consideramos central y le llamaremos origin, que es el nombre más típico y que la mayoría de usuarios de Git están acostumbrados a ver.

En la práctica, cuando uno hace commits en git realmente solo lo está haciendo a su repositorio local. Únicamente después de hacer un push estos son enviados al remoto (origin) y disponibles para los demás (tras resolver los conflictos).

centr-decentr

Las ramas principales

main-branchesEn resumen, el repositorio central mantiene dos ramas de vida infinita:

  • master
  • develop

La rama master de origin debe ser familiar para todos los usuarios de git. En paralelo a la rama master, existe una segunda rama llamada develop.

Consideramos origin/master como la rama principal donde el código de HEAD siempre, siempre, refleja un estado final o listo para producción.

Consideramos origin/develop como la rama principal donde el código de HEAD refleja siempre un estado con los últimos cambios en el desarrollo enviados para el próximo lanzamiento. Algunos lo llaman “rama de integración”. Desde aquí es desde donde se producirían las nightly builds.

Cuando el código en la rama develop alcanza un punto estable y está listo para ser lanzado, todos los cambios deben ser mezclados (merged) de vuelta en master de alguna forma y etiquetados (tagged) con un número de lanzamiento. Luego veremos cómo se hace esto.

De esta forma, cada vez que se sube un cambio a master, se trata de una nueva versión de producción por definición. Hay que ser muy estricto con esto, de manera que se pueda automatizar del todo y que sea posible, por ejemplo, crear un script que automáticamente recoja el código de master para compilar en cuanto se actualice.

Ramas de apoyo

Además de las ramas master y develop, se utilizan una serie de ramas de apoyo para ayudar al desarrollo paralelo entre miembros del equipo, para tener ubicadas las nuevas características, preparar código para el lanzamiento o ayudar a arreglar errores pequeños en producción. A diferencia de las ramas principales, estas ramas tienen siempre vidas limitada, ya que todas se acaban eliminando.

Los tres tipos de ramas de apoyo que usaremos principalmente son:

  • Feature branches (o de características nuevas)
  • Release branches (o de preparación de lanzamiento)
  • Hotfix branches (o de resolución de problemas inmediatos)

Cada una de ellas tiene un propósito específico y se rigen por ciertas reglas a la hora de creación, de dónde derivan y hacia dónde y cuándo vuelven. Por supuesto, hablamos de que son ramas especiales únicamente por el uso que hacemos de ellas, técnicamente son ramas iguales que las principales.

Feature branches (características)

featurebDeriva de:

develop

Debe mezclarse de vuelta en:

develop

Costumbres a la hora de nombrar:

Cualquier cosa excepto master, develop, release-* o hotfix-*

Las feature branches (algunas veces llamadas topic branches o ramas temáticas) se utilizan para desarrollar nuevas características para un lanzamiento futuro. Al comenzar el desarrollo de una nueva característica, el lanzamiento al que se incorporará posiblemente aún no se conozca. La esencia de una feature branch es que existe únicamente mientras esa característica concreta se está desarrollando, pero se mezclará de vuelta con develop (para añadirla definitivamente al próximo lanzamiento) o se descartará (en caso de que no salga bien)

Estas ramas normalmente existirán solo en los repositorios de los desarrolladores, no en origin.

Para crearlas utilizamos:

$ git checkout -b myfeature develop
Switched to a new branch "myfeature"

Y para incorporarla a develop y añadirla en la próxima versión:

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

La opción --no-ff ocasiona que la mezcla cree siempre un nuevo commit, incluso si fuese posible hacer un fast-forward. Esto es una buena práctica porque evita la pérdida de información sobre la existencia histórica de la rama y agrupa todos los commits que añadieron la característica nueva. Comparemos:

merge-without-ff

En el segundo caso es imposible ver desde el historial de git cuáles commits implementaron la nueva característica (salvo que te pongas a leer los mensajes uno a uno). Revertir toda una característica nueva (un grupo de commits) de esta manera sería muy difícil, mientras que en el primer caso, utilizando --no-ff, sería cosa de eliminar sólo el último commit de develop.

Es cierto que se crearían una cierta cantidad de commits casi vacíos, pero es un mal menor para las ventajas que aporta.

Release branches (lanzamientos)

Deriva de:

develop

Debe mezclarse de vuelta en:

develop y master

Costumbres a la hora de nombrar:

release-*

Las ramas de lanzamiento están pensadas para preparar el código para un lanzamiento. Debe ser una copia de la versión más estable de develop y únicamente sufrir cambios de última hora para arreglar pequeños bugs y añadir metadatos para el lanzamiento (número de versión, fecha de compilado, etc.). Con la creación de esta rama, la rama develop se ve libre para recibir nuevas características para una próxima versión. La rama de lanzamiento, una vez que se considera estable y lista para lanzar, debe mezclarse en master y también en develop, para que en esta última se vean reflejados los cambios y arreglos pequeños que se hayan realizado.

Para crearla y finalizarla:

$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

Creamos una rama desde develop, en este caso release-1.2, nos metemos en ella, hacemos cambios, que en este caso es un script llamado ./bump-version que podría hacer cualquier cosa, hacemos el commit una vez hecho el cambio, nos vamos a master, mezclamos (con --no-ff) y finalmente le ponemos una tag para nombrar la versión. Luego volvemos a develop, mezclamos también, y finalmente eliminamos la rama release-1.2.

Hotfix branches (arreglos inmediatos)

hotfix-branches
Deriva de:

master

Debe mezclarse de vuelta en:

develop y master

Costumbres a la hora de nombrar:

hotfix-*

Las ramas de hotfix son muy similares a las de lanzamiento, sólo que en este caso no son planificadas. Surgen de la necesidad de actuar de inmediato para arreglar un problema surgido en una versión de producción. Cuando aparece un bug crítico en una versión de producción y debe ser resuelto de inmediato, creamos una rama desde la versión más reciente de master y la arreglamos allí. La idea es que el equipo que trabaja en develop pueda continuar con su trabajo mientras otra persona hace los arreglos rápidos en master.

Muy similar a lo anterior:

$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)
$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

Se crea desde master, se hacen los cambios necesarios y se envían, luego se mezcla de vuelta en master y en develop, igual que la anterior, con una sola excepción: cuando haya una rama de release en marcha, se mezcla en esta en lugar de develop. Finalmente se elimina.

Resumen

En resumen, no es nada del otro mundo, ni es nada complicado, pero me parece un método muy efectivo para tener claro dónde está cada cosa, para qué sirve cada rama y no crearlas aleatoriamente y sin motivo, es una cuestión de orden y disciplina nada difícil de seguir.

Os recomiendo visitar el post original en inglés para alguna explicación más y saludar a su autor @nvie.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s