9 de Enero, 2005

Trabajando con RCS

Voy a hablar un poco de como se trabaja con RCS (Revision Control System), una herramienta de control de versiones disponible para casi cualquier sistema operativo (hasta para Win32, solo hay que buscar un poco) y que nos puede ayudar mucho a la hora de controlar los cambios que hacemos en un proyecto.

RCS es un sistema similar al CVS (Concurrent Versions System), con un repositorio (lugar donde se hace seguimiento de los cambios entre versiones), pero sin la parte de concurrencia. Es decir: resulta conveniente cuando no hay varios desarrolladores trabajando sobre el mismo fuente, que además pueden aplicar cambios de forma concurrente en el repositorio.

Así que para pequeños proyectos con uno o más desarrolladores, pero con solo una persona encargada de aplicar los cambios, RCS es una opción a considerar.

Empezamos a trabajar muy fácilmente. Creamos un directorio llamado RCS en el directorio del proyecto:

$ mkdir RCS
$ ls
hello.c  RCS/

El fichero hello.c contiene un típico hola mundo en C, que va a ser más que suficiente para esta pequeña introducción.

El contenido del fichero es el siguiente:

/* $Id$ */
#include<stdio.h>

static const char rcsid[]="$Id$";

int
main()
{
        print("Hola mundo!\n");
        return 0;
}

Como se puede ver casi es un ejemplo convencional de 'primer programa en C' (con un fallito para corregirlo luego ;)). Hay algunos elementos extraños que he marcado en color:

  • Rojo: Es un comentario al principio del fichero con una marca especial $Id$ que RCS modificará cada versión para reflejar el estado actual del fuente.
  • Azul: Es una variable estática que solo tiene significado para este fichero y que nos permitirá hacer seguimiento de la versión cuando el fichero esté compilado. Es el equivalente al comentario de la primera linea, pero para ficheros binarios.

Ninguno de los dos elementos es obligatorio, pero se consideran una buena costumbre. De nada sirve poder acceder al histórico completo de un proyecto si luego somos incapaces de identificar un fuente o un binario para saber con qué versión del proyecto se corresponde.

Ahora vamos a introducir el fichero en el repositorio. Ojo que no aplicamos cambios, sino metemos la primera versión. Esto habrá que hacerlo para cada fichero del proyecto y suele ser buena idea emplear los fuentes en un estado concreto e identificable (primera versión pública, por ejemplo; nunca con cosas a medias porque este es nuestro punto de partida).

$ ci hello.c
RCS/hello.c,v  <--  hello.c
enter description, terminated with single '.' or end of file:
NOTE: This is NOT the log message!
>> Programa que dice hola al mundo.
>> .
initial revision: 1.1
done

El comando ci (check in) nos informa de lo que está pasando y qué se espera de nosotros. Introducimos la descripción del fichero, acabada por un punto solitario a principio de linea, y RCS nos indica que la revisión ha sido marcada como 1.1.

Si miramos el contenido del directorio veremos como nuestro fichero ha desaparecido. Esto es porque se encuentra en el repositorio. Ahora si queremos trabajar con él, tendremos que sacarlo:

$ co -l hello.c
RCS/hello.c,v  -->  hello.c
revision 1.1 (locked)
done

Con el comando co (check out) sacamos el fichero indicado del repositorio, y con la opción -l indicamos además que es para editarlo.

En realidad un ci -l hello.c al principio nos hubiera llevado al mismo punto, pero me parece más fácil de entender así: ci mete en el repositorio y co saca del repositorio. Al final siempre haremos ci -l para evitar el paso del check out, porque es más cómodo.

Si no recordamos que ficheros hay en el repositorio cuando tenemos que hacer un co, podemos listar el contenido del directorio RCS sin problemas (los ficheros almacenados acaban en ,v).

Vamos a echarle un vistazo a nuestro fichero:

/* $Id: hello.c,v 1.1 2005/01/09 14:44:08 reidrac Exp reidrac $ */
#include<stdio.h>

static const char\
 rcsid[]="$Id: hello.c,v 1.1 2005/01/09 14:44:08 reidrac Exp reidrac $";

int
main()
{
        print("Hola mundo!\n");
        return 0;
}

He partido alguna linea para que quepa bien en la bitácora.

Como podemos observar el $Id$ ha sido reemplazado con gran cantidad de información: el nombre del fichero en el repositorio, la revisión actual, la fecha y hora en la que se creo la revisión, quién introdujo el fichero (reidrac), que la versión es experimental (Exp, se pueden emplear Rel para código que deja de estar en el repositorio y Stab para código estable; pero no por ahora trabajaremos solo con código experimental :P), y finalmente quién está editando el fichero en este momento.

Ya hemos preparado el repositorio para trabajar y además tenemos algunos conceptos claros (espero). Es el momento de hacer el trabajo propiamente dicho.

Hay muchas formas de trabajar con RCS, es decir, de usar la herramienta.

Ya que en el repositorio se guarda todo el historial de cambios (ahora veremos como acceder a él), podemos guardarlo como forma útil y ordenada de hacer copias de seguridad, haciendo un check in al final de cada jornada de trabajo indicando en el log los cambios realizados.

Otra forma, que es la que recomiendo porque nos obliga a seguir una disciplina de trabajo saludable y además maximiza la utilidad del repositorio, es que aplicaremos cambios concretos y de forma atómica. Si queremos hacer 2 cambios no relacionados, haremos uno y lo aplicaremos, y luego otro y lo aplicaremos. De esta forma podemos volver atrás cambio a cambio y nos permite que cada entrada del log contenga solo información útil para identificar el cambio que se realizó.

Vamos a hacer dos cambios: añadir una comentario con la licencia y cambiar el valor de retorno del programa.

Una vez añadido el comentario podemos comparar la versión de trabajo con la última del repositorio:

$ rcsdiff hello.c
===================================================================
RCS file: RCS/hello.c,v
retrieving revision 1.1
diff -r1.1 hello.c
1a2,4
>
> /* Este programa es de DOMINIO PUBLICO */
>

Ya hablé un poco cobre cómo funciona diff, y la salida de rcsdiff no es más que un diff de nuestra versión de trabajo contra la última del repositorio.

El cambio consiste en que he añadido 3 lineas comenzando en la linea 2.

Bien, como estamos satisfechos con el cambio que queríamos hacer, lo aplicamos:

$ ci -l hello.c
RCS/hello.c,v  <--  hello.c
new revision: 1.2; previous revision: 1.1
enter log message, terminated with single '.' or end of file:
>> Añadida licencia.
>> .
done

He empleado la opción -l para que no sea necesario hacer el co para seguir trabajando.

Vemos como RCS ha incrementado en número de revisión. Añadimos el segundo cambio, con lo que pasamos a revisión 1.3, y si echamos un vistazo al fichero veremos como se van actualizando las marcas de RCS.

Ahora vamos a ver como realizar algunas tareas básicas con el repositorio. Ya sabemos cómo ver las diferencias contra la versión anterior. También podemos ver las diferencias contra cualquier otra versión:

$ rcsdiff -r1.1 hello.c
===================================================================
RCS file: RCS/hello.c,v
retrieving revision 1.1
diff -r1.1 hello.c
1c1,4
< /* $Id: hello.c,v 1.1 2005/01/09 14:44:08 reidrac Exp $ */
---
< /* $Id: hello.c,v 1.3 2005/01/09 15:10:23 reidrac Exp reidrac $ */
>
> /* Este programa es de DOMINIO PUBLICO */
>
4c7
< static const char\
 rcsid[]="$Id: hello.c,v 1.1 2005/01/09 14:44:08 reidrac Exp $";
---
> static const char\
 rcsid[]="$Id: hello.c,v 1.3 2005/01/09 15:10:23 reidrac Exp reidrac $";
10c13
<       return 0;
---
<       return 1;

Y ahí tenemos las diferencias entre la versión de trabajo (1.3) y la primera revisión (1.1).

También puede ser útil recuperar una versión anterior. La opción -r tiene ese efecto en co, aunque además podemos obtener una versión anterior por fecha o tag.

Guardamos la versión de trabajo (que corresponde con la 1.3) y obtenemos la 1.2 para corregir un fallo (el del printf) y seguir desarrollando a partir de ahí:

$ ci hello.c
RCS/hello.c,v  >--  hello.c
file is unchanged; reverting to previous revision 1.3
done
$ co -r1.2 -l hello.c
RCS/hello.c,v  -->  hello.c
revision 1.2 (locked)
done
(editamos el fichero y corregimos el error)
$ ci -l -nHELLO_STABLE hello.c
RCS/hello.c,v  >--  hello.c
new revision: 1.2.1.1; previous revision: 1.2
enter log message, terminated with single '.' or end of file:
>> Corregido fallo printf
>> .
done

Además hemos añadido un tag para marcar que nuestro programa ya es estable :P, y podremos referirnos a esta versión con esa marca si lo deseamos.

Se ha indicado la revisión 1.2.1.1 porque... ¡hemos creado una rama alternativa de desarrollo!

Ha pasado algo así:

[1.1] ---> [1.2] ---> [1.3]
              \
               \----> [1.2.1.1]

Una vez que nuestro HELLO_STABLE ha sido probado, es cuestión de volver a la rama original de desarrollo. Para ello empleamos rcsmerge. Como tenemos ya nuestra HELLO_STABLE fuera, solo tenemos que pedirle que mezcle con la versión 1.3:

$ rcsmerge -r1.3 -p hello.c
RCS file: RCS/hello.c,v
/* $Id: hello.c,v 1.2.1.1 2005/01/09 15:28:44 reidrac Exp reidrac $ */

/* Este programa es de DOMINIO PUBLICO */

#include<stdio.h>

static const char\
rcsid[]="$Id: hello.c,v 1.2.1.1 2005/01/09 15:28:44 reidrac Exp reidrac $";

int
main()
{
        printf("Hola mundo!\n");
        return 0;
}

En este caso lo clava, pero no siempre será así. Por eso he empleado la opción -p para que muestre la salida en pantalla. Si hubieran problemas se marcarían los conflictos y a mano tendríamos que arreglar el fichero (se puede redirigir aun temporal para hacer las cosas con calma en lugar de aplicar sin el -p y tener que arreglar las cosas contra el repositorio).

Simplemente aplicamos los cambios:

$ rcsmerge -r1.3 hello.c
RCS file: RCS/hello.c,v
$ ci -r1.4 -l hello.c
RCS/hello.c,v  <--  hello.c
new revision: 1.4; previous revision: 1.3
enter log message, terminated with single '.' or end of file:
> Sincronización rama 1.2.1.1
> .
done

También podemos consultar el log desde una revisión concreta (empleando la opción -r como hemos visto en los comandos anteriores), o desde la primera revisión si no indicamos nada.

Y siguiendo la idea de ese comando, podemos obtener un Changelog del log de RCS mediante:

$ rcs2log
2005-01-09  jjm  <reidrac@homeworld>

        * hello.c: Sincronización rama 1.2.1.1

        * hello.c: Corregido fallo printf

        * hello.c: Cambio valor de retorno.

        * hello.c: Añadida licencia.

        * hello.c: New file.

Lo cual es muy útil a la hora de tener los cambios en un formato legible para publicar nuestro software.

Aun queda mucho más, pero con esto ya se puede empezar a trabajar. Una vez que se tienen dominados estos comandos solo falta ojear la página del manual de RCS para terminar de sacarle todo el partido a este sistema de control de revisiones, que sin ser tan complejo de montar como CVS, sí nos da la misma potencia.

Anotación por Juan J. Martínez.

Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.