Bazaar-NG (bzr), sistema de control de versiones distribuido

Bazaar-ng es un sistema de control de versiones distribuido que, al igual que CVS o Subversion, nos permite guardar progresivamente los cambios que vayamos realizando sobre un conjunto de archivos de texto (habitualmente código fuente), recuperar versiones anteriores, mostrar diferencias, integrar el trabajo de diversos programadores, etc…

Sin embargo, a diferencia de CVS o Subversion, Bazaar-ng nos permite trabajar de formas mucho más flexibles… desde el típico esquema cliente-servidor hasta la descentralización de los repositorios, tal y como veremos más adelante en el apartado de flujos de trabajo.

Para instalarlo en Ubuntu:

aptitude install bzr 

Veamos como podemos trabajar con Bazaar…

Términos

Antes de empezar a utilizar Bazaar-NG, debemos entender una serie de términos:

  • Working Tree: Código fuente con el que el programador puede trabajar. Todo árbol de trabajo está siempre asociado a una rama (branch).
  • Branch: Guarda el código fuente y el histórico de cambios que se han realizado sobre él. Adicionalmente, se nos permite crear nuevas ramas a partir de una rama, trabajar con ella de forma independiente (con su correspondiente control de versiones) y una vez hayamos acabado podemos volver a fusionarla con la rama inicial.

    Veamos un pequeño caso práctico: tenemos una rama base con el código estable y queremos añadir una nueva funcionalidad sin afectar a la estabilidad de ese código. Creamos una nueva rama de desarrollo basada en la rama estable, trabajamos con ella y una vez terminada la nueva funcionalidad y ha sido testeada, fusionamos con la rama estable.

  • Repository: Las ramas de un único proyecto son muy similares dado que son copias de la misma base junto a nuevas modificaciones, mantener toda esta información duplicada puede consumir bastante espacio innecesariamente. Si las ramas son guardadas en un repositorio, estas no mantendrán cada una sus revisiones sino que lo gestionará el repositorio, evitando así la duplicidad de información y optimizando el espacio usado.

Cabe destacar que es posible tener la rama (branch) y el árbol de trabajo (working tree) en un mismo directorio, o mantenerlos en directorios separados. Este último caso se suele utilizar para tener las revisiones de control en un servidor centralizado y los árboles de trabajo en los ordenadores de los desarrolladores.

Flujos de trabajo o Workflows

Como ya he apuntado previamente, con Bazaar-NG tenemos la posibilidad de trabajar de una forma más flexible que CVS o Subversion, adaptándose al flujo de trabajo que queramos utilizar:

  • Centralizado o Lock-Step: Concepto de trabajo igual que el aplicado por Subversion o CVS con la ventaja de una gestión de ramas mejorada mediante un repositorio.
  • Lock-Step con commits locales: Igual que el anterior pero los desarrolladores realizan commits locales sin modificar el servidor hasta que consideran oportuno actualizarlo. Tenemos la ventaja de reducir el número de commits erróneos que interfieran en el trabajo del resto de desarrolladores.
  • Descentralizado con linea principal compartida: Cada programador tiene su propia rama además de permisos en la rama principal. Cada uno trabaja en su rama personal (commit) y cuando lo consideran apropiado, lo fusionan con la rama principal (pull, merge). Ventajas:
    • Organización del trabajo sencilla.
    • Un desarrollador puede fusionar su rama con la de otra persona con la que este trabajando en algo conjuntamente.
  • Descentralizado con guardián automático o manual: Cada programador tiene su propia rama y permisos de solo lectura en la rama principal. Cuando un desarrollador quiere fusionar su código en la linea principal, lo pide al “guardián” que se encarga de revisar, compilar y hacer tests unitarios para comprobar que todo es correcto. La ventaja principal radica en que el código se revisa antes de pasar a la línea principal.

Configuración

La configuración de Bazaar-NG se debe crear en “~/.bazaar/bazaar.conf”:

mkdir ~/.bazaar/
cd ~/.bazaar/
touch bazaar.conf

Podemos editar “bazaar.conf” y añadir:

[DEFAULT]
email             = Your Name <email@isp.com>
editor            = /usr/bin/vim

Bazaar-NG requiere que cada desarrollador especifique su nombre e email para así registrar y vincular cada uno de los cambios que se realizan al código, adicionalmente, el hecho de proporcionar el email facilita que cualquier programador pueda ponerse en contacto con alguien que haya realizado un cambio determinado (en caso de proyectos donde participan muchas personas, es de agradecer).

Con la variable “editor” indicamos que editor queremos que se ejecute cuando Bazaar-NG requiere que proporcionemos información, por ejemplo la explicación de los cambios cuando queramos actualizar la rama.

En el fichero de configuración podemos añadir de forma opcional:

check_signatures  = check-available
create_signatures = when-required

Con check_signatures podemos indicar 3 valores:

  • require: Para cada actualización de código debe existir una firma gnupg y ser válida.
  • ignore: No se comprueban firmas gnupg.
  • check-available: (Valor por defecto) Si existe una firma gnupg para la actualización, comprobarla. Bazaar-NG fallará si encuentra una firma incorrecta, pero no si la firma no esta presente.

Con create_signatures podemos indicar también 3 valores:

  • always: Firmar cada actualización que enviemos.
  • when-required: (Valor por defecto) Firmar las actualizaciones que enviemos, sólo si la rama requiere que así lo hagamos.
  • never: No firmar las actualizaciones que enviemos, aunque la rama así lo requiera.

Como se puede intuir por las opciones, Bazaar nos permite firmar criptográficamente (con gnupg) los cambios que realicemos al código y comprobar a la vez, las firmas de los demás desarrolladores. Esta es una medida de seguridad efectiva para garantizar que los cambios son realmente de quien parecen ser, así evitamos que alguna persona mal intencionada intente inyectar código malicioso en nuestro proyecto.

Finalmente, indicar que hasta ahora hemos configurado la sección “Default” que es la configuración global para todos los repositorios. Podríamos crear configuraciones específicas por ramas:

[DEFAULT]
email             = Your Name <email@isp.com>
editor            = /usr/bin/vim
check_signatures  = check-available
create_signatures = when-required

[/home/usuario/rama01]
email             = Your Name <other_email@isp.com>

Creación de repositorios y ramas (Flujo de trabajo descentralizado)

Con tal de poder empezar a trabajar con Bazaar-NG siguiendo un esquema de trabajo descentralizado, tenemos 2 posibilidades:

  • Crear un repositorio y una rama base: Si en el futuro generamos nuevas ramas derivadas de la base dentro del repositorio, el historial de cambios será gestionado por el repositorio y no se duplicará información.


    Instrucciones (se creará “directorio-repo” para el repositorio y “estable” para la rama):

    ## Creación del repositorio en local donde las ramas y los directorios de trabajo estarán en el mismo directorio
    bzr init-repo --trees directorio-repo/
    
    # Creación de la rama base
    bzr init directorio-repo/estable
    	

    * El parámetro “–trees” crea un repositorio que contendrá ramas y directorios de trabajo conjuntamente, para que solo contenga ramas usaríamos “–no-trees”.

  • Crear directamente una rama base: Podemos crear una rama base sin estar contenida en ningún repositorio. En este caso, para cada rama derivada que creemos se duplicará el historial de cambios y por tanto será menos óptimo únicamente en términos de espacio.




    Instrucciones (se creará el directorio “estable” para la rama):

    # Creación de la rama base
    bzr init estable
    	

A partir de este punto, ya podemos trabajar en el directorio “estable” creando/copiando nuevos ficheros de código fuente.

Si quisiéramos crear ramas derivadas de la rama base, utilizaríamos:

# Creación de una rama
bzr branch directorio-repo/estable directorio-repo/desarrollo

Si estamos usando un repositorio, lo ideal es crear la nueva rama dentro del mismo repositorio para aprovechar así las ventajas ya comentadas. De hecho, podemos crear ramas derivadas de “desarrollo/” llamadas por ejemplo “usuario01”, “usuario02”, etc… así cada usuario trabajaría en su parte del desarrollo con ramas independientes que pueden fusionarse entre ellas. Estas nuevas ramas podrían situarse dentro del propio directorio “desarrollo/” tal y como muestra el esquema gráfico anterior. En instrucciones:

bzr branch directorio-repo/desarrollo directorio-repo/desarrollo/usuario01

En cualquier caso, también podemos crear ramas en cualquier otro directorio fuera del repositorio (de hecho, si no estamos usando repositorio, esto será lo habitual).

Fusión de ramas

Siguiendo los ejemplos de la sección anterior, es posible que nos interese fusionar el trabajo de la rama “desarrollo” en la rama “estable”:

# Juntar rama con la base
cd estable
bzr merge ../desarrollo
bzr commit

Creación de repositorios y ramas (Flujo de trabajo centralizado)

Si queremos empezar a trabajar con Bazaar-NG siguiendo un flujo de trabajo centralizado o Lock-Step con commits locales (ver sección de flujos de trabajo), necesitamos crear un repositorio y una rama base. Estas pueden ser creadas localmente tal y como se ha mostrado en la sección anterior, o en un servidor remoto utilizando SSH:

## Creación del repositorio en remoto donde las ramas no contendran los directorios de trabajo, y por tanto se debe hacer un branch/checkout para poder trabajar
bzr init-repo --no-trees sftp://centralhost/directorio-repo/

# Creación de la rama base
bzr init sftp://centralhost/directorio-repo/estable

En este punto hemos creado un repositorio centralizado con una rama en el directorio “estable”. Dado que el repositorio centralizado solo contiene una rama sin directorio de trabajo, no se puede trabajar directamente en el directorio “estable”, sino que debemos generarnos un directorio de trabajo aparte. Para ello utilizaremos el comando checkout, de esta forma estaremos forzando a Bazaar-NG a trabajar contra el servidor:

bzr checkout sftp://centralhost/directorio-repo/estable estable

Para la creación de ramas centralizadas, las generaremos dentro del repositorio del servidor para a continuación descargarlas mediante un chechout:

# Creación de una rama
bzr branch sftp://centralhost/directorio-repo/estable sftp://centralhost/directorio-repo/desarrollo

bzr checkout sftp://centralhost/directorio-repo/desarrollo desarrollo

Bazaar-NG nos permite realizar dos tipos de checkout diferentes:

  • LightweightCheckout: Unicamente se descarga el código:
    bzr checkout --lightweight sftp://centralhost/directorio-repo/estable estable
    	
    • Ventajas: Consume menos ancho de banda.
    • Desventajas: Tenemos que trabajar siempre con acceso al servidor remoto.
  • HeavyweightCheckout: Se descarga el código y el historial.
    bzr checkout sftp://centralhost/directorio-repo/estable estable
    	
    • Ventajas: Si no tenemos acceso al servidor, podemos ir guardando los cambios a nuestro código de forma local (parámetros: commit –local) o desasociarse del servidor (parámetro: unbind). Si hemos desasociado y más adelante volvemos a tener conexión con el servidor, podemos volver a asociarnos (parámetro: unbind) y actualizar ahora el servidor.
    • Desventajas: Consume más ancho de banda.

Situaciones útiles para trabajar de forma centralizada:

  • Múltiples programadores que quieren trabajar con un código constantemente actualizado entre ellos.
  • Un único programador que utiliza diferentes máquinas y quiere mantener el código sincronizado en todas ellas

Uso de Bazaar-NG

Hasta este momento hemos visto como configurar Bazaar-NG, crear repositorios y ramas para flujos de trabajo centralizados y descentralizados. En este sección nos centraremos en el uso habitual de la herramienta una vez ya disponemos de todo lo necesario para trabajar.

Para familiarizarnos con la herramienta, seguiremos todo un conjunto de instrucciones partiendo de la creación de una rama base local:

mkdir test
cd test
bzr init

Dentro de esta rama “test” podemos ver su estado:

bzr status

Como no hemos hecho ningún cambio, este comando no visualizará nada. Probamos a crear un fichero vacio:

touch main.cs
bzr status

Esta vez Bazaar-NG nos indicará que hay un fichero que no se encuentra gestionado por el control de versiones:

unknown:
  main.cs

Otra forma de ver este tipo de información:

bzr unknowns

Para incluir el fichero “main.cs” en el control de versiones:

bzr add main.cs 

Añadimos una línea al ficheor “main.cs”:

$ echo "Hola mundo" >> main.cs

Ahora nuestro directorio de trabajo contiene modificaciones respecto al control que lleva Bazaar-NG, si queremos mostrar las diferencias:

bzr diff

Lo que nos mostrará:

=== added file 'main.cs'
--- main.cs     1970-01-01 00:00:00 +0000
+++ main.cs     2006-10-18 20:39:03 +0000
@@ -0,0 +1,1 @@
+Hola mundo

En este momento podemos actualizar el sistema de control de versiones con una nueva versión de nuestro código, para ello debemos especificar un mensaje descriptivo con los cambios que hemos realizado al código:

bzr commit -m "hola mundo en main.cs"

Si no indicasemos el mensaje con el parámetro “-m”, se ejecutaría el editor del sistema (ver sección configuración) para que introdujesemos la descripción. Una vez realizado el commit, por pantalla se mostrará:

added main.cs
Committed revision 1.

Acabamos de guardar la primera revisión de nuestro código.

Imaginemos que por error borramos el fichero “main.cs”:

rm main.cs

Podemos recuperarlo mediante:

bzr revert ## Recupera la última versión

Es posible que a medida que trabajemos en nuestro proyecto, haya ficheros que queremos que sean ignorados por Bazaar-NG, como por ejemplo las típicas cópias de seguridad de algunos editores (ficheros acabados en *~). Para ello creamos el fichero “.bzignore” con los patrones de ficheros a ignorar:

echo "*~" >> .bzrignore

Para probar la configuración, creamos un fichero que cumple el patrón y indicamos a Bazaar-NG que nos liste los ficheros que serán ignorados:

touch main.cs~
bzr ignored

Efectivamente nos tiene que mostrar “main.cs~”.

Por comodidad, el fichero de configuración de ignorados “.bzignore” también debe ser añadido al sistema de gestión de versiones:

bzr add .bzrignore
bzr commit -m "Add ignore patterns"

Acabamos de incorporar la segunda revisión.

Si queremos visualizar las diferencias entre revisiones podemos usar:

# Diferencias entre la revisión 0 y la 2
bzr diff -r 0..2 

# Diferencias entre la revisión 0 y la última
bzr diff -r 0..

# Diferencias entre la revisión inicial y la 2
bzr diff -r ..2

A continuación, creamos un directorio “src” con un fichero “simple.c” y lo añadimos:

mkdir src
echo 'int main() {}' > src/simple.c
bzr add src

Bazaar-NG añade de forma recursiva todo el contenido del directorio:

added src
added src/simple.c

Añadimos la nueva revisión con el nuevo directorio y fichero:

bzr commit -m "Simple!"

Si en este punto borramos por completo el directorio “src” y preguntamos a Bazaar-NG cual es el estado:

rm -rf src/
bzr status

Detectará automáticamente ficheros borrados físicamente:

removed:
  src/
  src/simple.c

Y en el próximo commit los considerará como borrados. Por otro lado, si creamos un fichero como por ejemplo “complex.c”:

echo 'int main() {}' > complex.c
bzr add complex.c
bzr commit -m "Borrado src"

Y lo intentamos borrar mediante:

bzr remove complex.c

Bazaar-NG hace que el fichero deje de estar versionado, pero no lo borra fisicamente del directorio actual. Debemos ser nosotros quienes llevemos a cabo esa tarea de forma específica, igual que hicimos con el directorio “src”.

Ahora creamos una rama a partir de la rama “test” tal y como hemos visto en secciones anteriores:

cd ..
bzr branch test/ test-rama-derivada

Ahora podriamos entrar a la nueva rama:

cd test-rama-derivada

Y veriamos que contiene lo mismo que la rama original “test”. Podriamos trabajar en esta rama mientras otras personas trabajan en la “test” en paralelo, e ir actualizando nuestra rama con la rama “test” mediante:

bzr pull

Si han habido cambios en “test”, los intenta integrar en “test-rama-derivada” (que es donde hemos ejecutado el comando anterior).

Por el contrario, si lo que queremos hacer es actualizar “test” con el contenido de “test-rama-derivada” ejecutaremos:

bzr merge

En caso de que aparezcan conflictos, por ejemplo un fichero llamado “file.c” ha sido modificado en ambas ramas, se generarán en nuestra rama local los siguientes ficheros:

  • file.c: Fichero mezclado con las 2 versiones y señalado el punto de conflicto mediante símbolos “<<<” y “>>>”.
  • file.c.BASE: Versión anterior del control de versiones de tu rama.
  • file.c.OTHER: Versión de la otra rama.
  • file.c.THIS: Versión local de tu rama.

Podemos utilizar programas como diff (consola) o meld (gráfico) para ver las diferencias entre el fichero OTHER y THIS, quedandonos con los cambios que nos convengan (quizás este es un buen momento en el que se tiene que contactar con los otros programadores para decidir que hacer). Una vez tengamos la versión definitiva, la guardamos en “file.c” y borramos los otros 3 ficheros (BASE, OTHER y THIS).

A continuación se indica a Bazaar-NG que el conflicto ha sido resuelto, hacemos commit a nuestra rama e intentamos volver a hacer el merge:

rm *.BASE *.OTHER *.THIS
bzr resolve file.c
bzr commit -m "conflictos resueltos"
bzr merge

Para evitar los conflictos, lo ideal es que se defina muy bien el objetivo de cada desarrollador, dividiendo las tareas a realizar y evitando solaparse trabajando sobre los mismos ficheros. Pero llegado el caso, ya hemos visto que puede ser sencillo controlar los posibles conflictos que puedan surgir con Bazaar-NG.

Herramientas gráficas

Si queremos disponer de ayudas visuales para realizar las tareas más habituales, podemos instalar el paquete “bzr-gtk”:

aptitude install bzr-gtk

De esta forma tendremos nuevos parámetros para el comando “bzr”:

  • “bzr gcommit”: Nos mostrará gráficamente los ficheros que se van a subir y tendremos una caja de texto donde añadir la pertinente descripción.
  • “bzr gdiff”: Podremos ver en una ventana y con resaltado de sintaxis las diferencias entre las revisiones que indiquemos
  • “bzr visualise”: Muestra gráficamente el historial de cambios listando programadores, descripciones de cambios, conflictos, etc…
  • “bzr gannotate fichero“: Muestra gráficamente los cambios realizados a un fichero en concreto durante la diferentes revisiones.
  • “bzr gbranch”: Nos permite crear una rama a partir de otra.

Finalmente, para visualizar y tratar diferencias entre ficheros, recomiendo la aplicación meld.

One thought on “Bazaar-NG (bzr), sistema de control de versiones distribuido

Leave a Reply

Your email address will not be published. Required fields are marked *