Curso Subversion: Ramas y fusiones

Ramas y fusiones en subversion

Qué es una rama

Es una línea de desarrollo que existe independientemente de otra, pero comparte una misma historia si se mira suficientemente atrás en el tiempo.

Una rama siempre comienza como una copia de algo. Lo bueno es que al compartir una historia común, subversion proporciona herramientas para pasar cambios que se han hecho en una rama a cualquier otra rama paralela. Es decir se pueden hacer mezclas de contenido entre ramas.

Si se pretende usar ramas y fusiones en un repositorio es muy recomendable que dicho repositorio se organice a partir de, al menos, dos carpetas: trunk y branches son los nombres típicos y recomendados. También se puede añadir la carpeta tags si se desea etiquetar revisiones (esto se verá en la siguiente unidad).

Layout recomendado:

repo
|- trunk
|- branches
`- tags

Creando una rama

Supongamos que queremos probar a añadir una nueva funcionalidad al software sin interferir para nada con el desarrollo base del mismo. Una vez que la finalicemos, si vemos que merece la pena, queremo reintegrarla con el desarrollo base. Es una buena razón para crear una nueva rama.

Las ramas son copias que se hacen directamente sobre el repositorio remoto:

svn cp http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk \
       http://curso-svn.juandarodriguez.es/svn/mapbundle/branches/nuevo_algoritmo_ordenacion \
       -m 'creación de la rama para probar un nuevo algoritmo de ordenación'

Y para trabajar en esa rama bastaría hacer un checkout de la misma:

svn co http://curso-svn.juandarodriguez.es/svn/mapbundle/branches/nuevo_algoritmo_ordenacion

O realizar un switch sobre una copia de trabajo:

svn switch http://curso-svn.juandarodriguez.es/svn/mapbundle/branches/nuevo_algoritmo_ordenacion

Cuando se van haciendo cambios en una y otra rama, al hacer svn log se puede comprobar como ambas ramas muestran todos los commits que se hicieron justo antes de la creación de la rama, incluidos los de las ramas comunes justo antes de hacer la ramificación.

Es importante recordar que:

En subversion una rama no es más que una copia. El que sea una rama es un significado que el desarrollador le da, ya que no es más que otro directorio en el repositorio con la peculariedad de que comparte una historia común con el directorio origen de la copia.

Fusionando

Fusionar (merge) es llevar cambios realizados en una rama a otra. Una fusión se puede realizar una vez finalizado el trabajo en una rama, o poco a poco, a medida que se va trabajando en las ramas.

El problema de hacerlo todo del tirón es que la cantidad de conflictos crece a medida que las ramas se van "separando". Cuanto más tiempo pasemos sin fusionar, mayor será la cantidad de conflictos que se producirán en la fusión.

Por eso es aconsejable ir fusionando poco a poco. Es un caso bastante frecuente en el desarrollo de software que estemos probando algo o desarrollando una nueva funcionalidad en una rama distinta de la base (trunk) pero deseemos incorporar los cambios que se vayan haciendo en trunk, para mantener la rama sincronizada.

Changesets

Un changeset es una colección de cambios con un único nombre. Es simplemente un patch (parche) con un nombre con que referirnos.

En subversion un número de revisión N sirve como nombre de un árbol en el repositorio: es la forma en que el repositorio se muestra después del N-ésimo commit. Es implicitamente también el nombre de un changeset: comparando la revisión N con la N-1 sabemos los cambios exactos que se han llevado a cabo. Por tanto podemos pensar en ese número N, no solo en el nº de revisión si no también como el nombre de un changeset.

El comando svn merge es capaz de usar números de revisiones. Se puede mezclar changesets específicos de una rama a otra pasándolo como argumento en dicho comando. Por ejemplo: svn merge -c 9238 mezclaría el changeset r9238 con la copia de trabajo (-c compara la revisión en cuestión, 9238 en este caso, con la inmediatamente anterior).

Caso de uso típico: mantener una rama en sincronía con la rama trunk

Llega el momento de comenzar a trabajar en una nueva funcionalidad y hacemos una rama para ello:

svn cp http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk \
       http://curso-svn.juandarodriguez.es/svn/mapbundle/branches/nuevo_algoritmo_ordenacion \
       -m 'creación de la rama para probar un nuevo algoritmo de ordenación'

Cuando, por ejemplo, se corrige un bug en la rama trunk , es lógico que se quiera mezclar los cambios a la rama que hemos creado para desarrollar la nueva funcionalidad. Para ello, desde una copia de trabajo de esa rama se hace primero svn update para garantizar que no hay cambios en dicha copia y se ejecuta después:

svn merge http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk

Todos los cambios que se hayan realizado en trunk se mezclan en la rama nuevo_algoritmo_ordenacion. Es posible que haya que resolver conflictos, al fin y al cabo un merge es tabién una actualización. Ahora hay que comprobar que todo sigue funcionando antes de hacer el commit.

svn commit -m 'fusiono con trunk para arreglar bug 123'

Y se continúa trabajando.

Se arregla otro bug en trunk, y se quiere pasar a la nueva rama. Importante hacer un svn update en la rama nuevo_algoritmo_ordenacion antes de hacer la mezcla. Y se repite:

svn merge http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk
svn commit -m 'fusiono con trunk para arreglar bug, r124'

Subversion sabe qué revisión se ha mezclado ya y realiza la mezcla solo de los cambios ocurridos desde entonces.

Y así vamos manteniendo en sincronía la rama de nuevo_algoritmo_ordenacion con la rama trunk.

Caso de uso típico: reintegrar la nueva versión en la versión de producción

Cuando se ha finalizado el desarrollo de la rama nuevo_algoritmo_ordenacion y hemos decidido que la nueva funcionalidad debería formar parte de la rama trunk podemos pasar todos los cambios a la rama trunk.

Si se han ido sincronizado todos los cambios hechos en trunk a la nueva versión (y solo si se han pasado TODOS), podemos hacer lo que se denomina reintegrar la rama de desarrollo.

En una copia de trabajo de trunk, completamente limpia, sin modificaciones ni mezcla de versiones:

svn merge --reintegrate http://curso-svn.juandarodriguez.es/svn/mapbundle/branches/nuevo_algoritmo_ordenacion

Importante, una vez que se hace una reintegración, la rama de pruebas ya no es utilizable para seguir trabajando. Ya no es posible que absorba nuevos cambios desde trunk, ni puede ser reintegrada a trunk de nuevo. Por tanto, si se quiere continuar trabajando en la rama de pruebas, se recomienda destruirla y crearla de nuevo desde trunk.

svn delete http://curso-svn.juandarodriguez.es/svn/mapbundle/branches/nuevo_algoritmo_ordenacion.1 \
         -m "Borro nuevo_algoritmo_ordenacion, reintegrada con trunk en r21."

svn copy http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk \
       http://curso-svn.juandarodriguez.es/svn/mapbundle/branches/nuevo_algoritmo_ordenacion \
       -m "recreo la rama nuevo_algoritmo_ordenacion"

...

Mergeinfo y previews

El mecanismo que usa subversion para llevar la cuenta de los changesets, es decir qué cambios han sido mezclado en qué ramas, es registrando datos en propiedades versionadas. Concretamente, los datos sobre las mezclas se almacenan en la propiedad svn:mergeinfo, vinculada a ficheros y directorios.

svn propget svn:mergeinfo .
svn propget svn:mergeinfo . --recursive

Además el comando

svn mergeinfo http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk

Da información sobre las revisiones que han sido mezcladas

y para saber las que todavía se pueden usar para mezclar:

svn mergeinfo http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk --show-revs eligible

Deshaciendo cambios mezclados y subidos al repositorio

Esto se puede hacer con el propio comando merge, pero indicando la revisión en negativo:

svn merge http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk -c -28

es lo que se llama una mezcla inversa.

Resucitando un fichero borrado

Se puede hacer con el comando svn copy:

svn copy http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk/bootstrap.php@12 ./bootstrap.php

Recuperándolo así el fichero mantiene la historia. Si no queremos mantenerla podemos recuperar el fichero así:

svn cat http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk/bootstrap.php@12 > ./bootstrap.php

Caso de uso típico: cherrypicking

El término cherrypicking en el contexto de los controles de versiones hace referencia a la operación de fusionar changesets concretos de otras ramas. Se hace aplicando el modificado -c y el nº de revisión (changeset) sobre la copia de trabajo:

svn merge -c 12 http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk

Habitualmente se suele examinar previamente las diferencias del changeset en cuestión con la copia de trabajo:

svn diff -c 12 http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk

Caso de uso típico: traerse solo una parte de los cambios (subtree merge)

En trunk se cambian dos ficheros en una misma revisión y se suben al repo.

Podemos ver los cambios con la aplicación websvn.

Ahora podemos traernos solo la parte del árbol que nos convenga:

svn merge http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk/module/Guayshot/src/Services/AlbumEncoder.php
\  module/Guayshot/src/Services/AlbumEncoder.php

Y solo se mezcla ese fichero. Si fuera un directorio se traería todo su contenido.

Si más adelante queremos fusionar lo que no hemos fusionado ahora, haríamos:

svn merge http://curso-svn.juandarodriguez.es/svn/mapbundle/trunk

Y se trae todo lo que no se trajo en el momento en que hicimos la mezcal parcial. Lo que ya se trajo lo deja como tal.

Revision Keywords

El cliente de subversion entiende ciertas palabras claves con un significado concreto. Es importante conocerlas para interpretear correctamente la documentación y los muchos de los mensajes que arroja dicho cliente cuando se opera con él:

HEAD
La última (o más "joven") revisión en el respositorio.
BASE
El número de revisión de un item en una copia de trabajo. Si el item ha sido modificado localmente, BASE se refiere a la forma en que el item aparece sin estas modificaciones locales.
COMMITTED
La revisión más reciente antes o igual a BASE, en el cual un item cambió.
PREV
La revisión inmediatamente antes de la última revisión en la cual un item cambió. Es lo mismo que COMMITTED-1.

Patrones comunes para la organización de ramas

Ramas para versiones de lanzamiento (release branches)

  1. Los desarrolladores realizan todo el nuevo trabajo sobre la rama trunk. Los cambios del día a día se hacen en esta rama; nuevas funcionalidades, corrección de bugs, etcétera.
  2. Cuando el software está listo para realizar un lanzamiento (release), se crea una rama con el nombre de la release (por ejemplo: /branches/version-1.0).
  3. El desarrollo se va llevando en paralelo, un equipo se centra en depurar y testear bien la versión de lanzamiento y otro continua el desarrollo hacia la siguiente versión. Probablemente haya que pasar cambios de la rama de lanzamiento a la master, especialmente los bugs descubiertos, pero también puede ocurrir que haya cambios en trunk que haya que pasar a la rama de lanzamiento.
  4. Llega el momento en que la rama de lanzamiento está suficientemente estable como para ser distribuida o colocada en producción. Entonces se etiqueta la revisión final, por ejemplo como tags/release_1.0.0.
  5. Seguro que se seguirán descubriendo bugs en la versión 1.0, y se irán corrigiendo en branches/version-1.0, y cuando se hayan resuelto un nº suficiente de bugs, se volverá a etiquetar la última revisión de la rama version-1.0, por ejemplo como tags/relesase_1.0.1.
  6. Se sigue manteniendo la rama en el tiempo, hasta que se decida finalizar su mantenimiento.

Mientras se ha seguido el desarrollo de la rama trunk y llegará el momento de lanzar la nueva versión 1.1, repitiendose el proceso. A lo largo de los años el repositorio contará con varias ramas de lanzamiento, algunas de ellas con mantenimiento y otras sin y un conjunto de etiquetas que se corresponden con las releases del software.

Ramas para desarrollo de funcionalidades (feature branches)

Las feature branches son los tipos de ramas más típicos y se hacen para desarrollar una funcionalidad sin interferir en la rama trunk. Estas ramas se deben mantener en sincronía con la rama trunk y reintegradas en esta última rama una vez que se finaliza el trabajo.

Los ejemplos que hemos visto en Caso de uso típico: mantener una rama en sincronía con la rama trunk y Caso de uso típico: reintegrar la nueva versión en la versión de producción se corresponden con este modelo.

La decisión de crear una rama para el desarrollo de una funcionalidad depende de la politica de desarrollo del proyecto. Si son muy conservadoras, se harán incluso para corregir un bug, si son más flexibles, se harán solo cuando se preveea que el desarrollo dará lugar a un número demasiado alto de commits que desestabilizan la rama trunk.

Ramas para librerías de tercero (vendor branches)

Cuando se utilizan librerías de terceros, si estas librerías se encuentran en un proyecto de subversion asociado y su mantenimiento se va a dejar en manos de los creadores de la librería, es decir, no necesitamos modificarla para nada, lo mejor es utilizar externals.

Pero si se da el caso de que necesitamos realizar algunas modificaciones se puede utilizar la siguiente estrategia.

Se crea en la raíz del repositorio un directorio vendors y ahí se colocan la/s librería/s de terceros que necesitemos. Entonces se etiqueta esa versión.

Posteriormente se hace una copia (rama) de la revisión etiquetada de estos vendors en el directorio trunk y es ahí donde realizamos las modificaciones que se precisen. Cuando haya una nueva versión de la librería por parte de los creadores/mantenedores, se sustituye el código de la librería en vendors por el nuevo, y se vuelve a crear una etiqueta para esta nueva versión.

Entonces se mezclan los cambios que haya entre las dos etiquetas con la copia (rama) que hicimos en trunk. Habrá que resolver condflictos con casi toda seguridad, pero de esa manera tendremos los cambios que los creadores de la librería han introducido en la nueva versión y los que nosotros hemos añadido.

Y podremos continuar aplicando este patrón con las sucesivas versiones que vayan saliendo de la librería.

Aquí puedes encontrar un ejemplo bastante claro de este patrón.

Etiquetas >>>