Los programas nunca son tan fáciles como se piensa que son al principio. Cualquier buen programa va más allá de su propósito original. Con el tiempo hay cambios, se agregan cosas, se solucionan errores y se hacen mejoras.
Los comentarios son una buena manera de mantener información acerca de los cambios, pero para cualquier trabajo serio se necesita alguna forma de control de versiones. Supongamos que cambias el programa juego.c, agregándole varias características. Un año después, tu cliente más importante te llama y te dice que no quiere todas esas características: que quiere la versión original del año pasado, en la que se había arreglado un error.
En Linux existe RCS (Sistema de Control de Revisiones). RCS es una colección de comandos que le permiten rastrear cambios realizados en archivos, recuperar cualquier versión anterior y comparar las versiones actuales con las más antiguas.
RCS
Los principales comandos de la suite RCS se muestran a continuación:
ci: Insertar en el depósito una nueva versión de un archivo.
co: Obtener la última versión de un archivo.
ident: Buscar identificadores de RCS en archivos.
merge: Crear una versión de un archivo que incorpore cambios de otras dos versiones de ese archivo.
rcsdiff: Comparar dos versiones de un archivo.
rlog: Ver el historial de un archivo.
RCS mantiene en un depósito el historial de las revisiones de los archivos. Por lo general, ese depósito es un directorio llamado RCS, que se encuentra en su directorio actual.
En el siguiente ejemplo, iniciamos el historial de RCS de un archivo en nuestro proyecto de tirar dados:
$ ci makefile
RCS/makefile,v <-- makefile
Enter description, terminated with single '.' or end file:
NOTE: This is NOT the log message!
>> Makefile del programa de tirar dados
>> .
initial version 1.1
done
$
Ahora hemos registrado el Makefile en el depósito de RCS, y RCS ha creado un archivo en el directorio RCS llamado Makefile,v. A medida que modifiquemos el Makefile y verifiquemos las versiones más recientes, RCS levará el registro de esos cambios en su copia Makefile,v.
Nota: Después de registrar un archivo en RCS, verás que tu archivo original ha desaparecido. No te asustes (gallina), no lo has perdido. RCS ha rastreado sus cambios en su copia y ha eliminado su original. Aún puede revisar su archivo con el comando co.
Registra todos los archivos necesarios para crear el programa de los dados, con los comandos ci juego.c y ci tirador.c.
$ ci *.c
RCS/juego.c,v <-- juego.c
Enter description, terminated with single '.' or end file:
NOTE: This is NOT the log message!
>> Archivo principal del programa tirar dados
>> .
initial version 1.1
done
RCS/tirador.c,v <-- tirador.c
Enter description, terminated with single '.' or end file:
NOTE: This is NOT the log message!
>> Archivo que contiene implementacion de la funcion tirardado()
>> .
initial version 1.1
done
$ ls
RCS
$
Piensa en RCS como si fuera una biblioteca que guarda tus archivos. Puedes sacar copias de solo lectura con el comando co nombrearchivo. Cuando quieras modificar un archivo, puedes sacar un copia el la que se pueda escribir (bloqueada) con co -l. Puedes sacar cualquier cantidad de copias de sólo lectura (desbloquedas) a la vez. Sólo puedes sacar una copia bloqueada a la vez. Por ejemplo:
$ co ./juego.c
./RCS/juego.c,v --> ./juego.c
revisison 1.1
done
$ ls -l
total 8
-r--r--r-- 1 root root 466 2007-02-28 14:41 juego.c
drwxr-xr-x 2 root root 4096 2007-02-28 14:37 RCS
En este caso sacamos el archivo juego.c como solo lectura. Si utilizamos el comando ls -l veremos en la parte de permisos -r--r--r-- lo que quiere decir que no podemos editar dicho archivo.
Hay varias palabras reservadas de identificación que puedes colocar en tu archivo y que son reconocidas por RCS. Estas palabras reservadas empiezan y terminan con $. Podriamos, por ejemplo, modificar el programa juego.c así:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int tirarDado(void);
int main(int argc, char * argv[])
{
int i, iIter, dado[6];
if(argc<2)
{
printf("Uso: %s n\n",argv[0]);
return 1;
}
iIter = atoi(argv[1]);
memset(dado, 0, sizeof(dado));
for(i=0; i<iIter; i++)
{
dado[tirarDado() - 1]++;
}
printf("%d tiradas\n",iIter);
printf("\tCara\tTiradas\n");
for(i=0; i<6; i++)
{
printf("\t%d : \t%d\n",i+1, dado[i]);
}
printf("$Header$\n");
}
Para ello tendría que sacar el archivo bloqueado (con permisos de escritura), así:
$ co -l ./juego.c
./RCS/juego.c,v --> ./juego.c
revisison 1.1 (locked)
done
$ ls -l
total 8
-rw-r--r-- 1 root root 466 2007-02-28 14:43 juego.c
drwxr-xr-x 2 root root 4096 2007-02-28 14:43 RCS
Ahora sí podemos editar el archivo, ya que tiene permisos de lectura/escritura. Observemos que cuando saco el archivo desbloqueado (sólo lectura), RCS reemplaza la palabra reservada $Header$ con información acerca del nombre y la versión del archivo. Cuando lo saco bloqueado, RCS no reemplaza el encabezado.
Por último meto de nuevo el programa juego.c (ya modificado), creo el programa y lo ejecuto:
ci ./juego.c
./RCS/juego.c,v <-- ./juego.c
new revision: 1.2; previous revision: 1.1
enter log message, terminated with single '.' or end of file:
>> Le he agregado el Header
>> .
done
$ ls
RCS
$ make
co RCS/makefile,v makefile
RCS/makefile,v --> makefile
revision 1.1
done
co RCS/juego.c,v juego.c
RCS/juego.c,v --> juego.c
revision 1.2
done
cc -c juego.c
co RCS/tirador.c,v tirador.c
RCS/tirador.c,v --> tirador.c
revision 1.1
done
cc -c tirador.c
cc -O -o juego juego.o tirador.o
$ ls -l
total 40
-rwxr-xr-x 1 root root 12689 2007-02-28 14:52 juego
-r--r--r-- 1 root root 562 2007-02-28 14:52 juego.c
-rw-r--r-- 1 root root 1368 2007-02-28 14:52 juego.o
-r--r--r-- 1 root root 247 2007-02-28 14:52 makefile
drwxr-xr-x 2 root root 4096 2007-02-28 14:46 RCS
-r--r--r-- 1 root root 65 2007-02-28 14:52 tirador.c
-rw-r--r-- 1 root root 804 2007-02-28 14:52 tirador.o
$ ./juego 130
130 tiradas
Cara Tiradas
1 : 18
2 : 22
3 : 28
4 : 27
5 : 17
6 : 18
$Header: /mnt/doc/RCS/juego.c,v 1.2 2007/02/28 13:46:14 root Exp $
$
También podemos buscar identificadores dentro de un archivo, por ejemplo:
$ co ./juego.c
./RCS/juego.c,v --> ./juego.c
revisison 1.2
done
$ ident juego.c
juego.c:
$Header: /mnt/doc/RCS/juego.c,v 1.2 2007/02/28 13:46:14 root Exp $
Es posible ver que cambios se han realizado de una versión a otra, por ejemplo:
$ rlog ./juego.c
RCS file: ./RCS/juego.c,v
Working file: ./juego.c
head: 1.3
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 3; selected revisions: 3
description:
Archivo principal del programa tirar dados
----------------------------
revision 1.3
date: 2007/02/28 14:32:09; author: root; state: Exp; lines: +2 -2
Le puse "numero de tiradas" en vez de "tiradas"
----------------------------
revision 1.2
date: 2007/02/28 13:46:14; author: root; state: Exp; lines: +3 -3
Le he agregado el Header
----------------------------
revision 1.1
date: 2007/02/28 13:36:34; author: root; state: Exp;
Initial revision
CVS
Para los que no sepan que es CVS, una pequeña introducción...
El Concurrent Versions System (CVS), también conocido como Concurrent Version System o Concurrent Versioning System, es una aplicación informática que implementa un sistema de control de versiones: mantiene el registro de todo el trabajo y los cambios en los ficheros (código fuente principalmente) que forman un proyecto (de programa) y permite que distintos desarrolladores (potencialmente situados a gran distancia) colaboren. CVS se ha hecho popular en el mundo del software libre. Sus desarrolladores difunden el sistema bajo la licencia GPL. Fragmento tomado de la Wikipedia.
Pero... ¿porqué RCS y no CVS? o ¿porqué CVS y no RCS? RCS es un sistema similar al CVS (Concurrent Versions System), de hecho CVS trabaja sobre RCS, con un repositorio (lugar donde se hace seguimiento de los cambios entre versiones), pero sin la parte de concurrencia. Es decir: RCS resulta conveniente cuando no hay varios desarrolladores trabajando sobre el mismo código 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.
Antes de empezar debemos tener claros ciertos conceptos:
repositorio: jerarquía de directorios alojada en el servidor que contiene diferentes módulos a disposición de los usuarios.
módulo: árbol de directorios que forma parte del repositorio; cuenta con un nombre identificador gracias al cual podremos bajárnoslo de forma selectiva.
La instalación la puedes hacer a través de apt-get, urmpi, emerge, o con el tarball. Lo más sencillo sería, si tienes Debian o un derivado, hacer:
apt-get install cvs cvs-doc cvsbook
Invocar a cvs
cvs es un programa que se invoca desde intérpretes de órdenes. según cómo sea su configuración (y estoy pensando en las diferentes formas de autenticación) lo podrás usar en procesos por lotes sin ningún problema.
Un aspecto que debes tener en cuenta (sobre todo si este es el primer documento que lees sobre cvs) es que cvs tiene parámetros para cada una de sus órdenes. para conocerlas tienes dos métodos: invocar cvs como «cvs help» o mirar la ayuda.
Configuración
Puedes usar varios ficheros de configuración que cvs reconocerá y usará. Los más importantes:
~/.cvsignore
que contiene los sufijos de los ficheros que no nos interesa que cvs controle: *.tex *.aux *.dvi *.ps *.log
~/.cvsrc
que contiene parámetros que cvs usará cada vez que se invoque:
cvs -z 3
update -Pd
diff -uw
La autenticación
Al trabajar en remoto con cvs pueden elegirse varias alternativas de autenticación (es decir, de demostrar al servidor que somos quienes decimos que somos). Las que más he visto usar son vía pserver y vía ssh. Deberás elegir alguna de estas técnicas en función de tus necesidades, las ganas que tengas de complicarte la vida y, sobre todo, del grado de neurosis obsesiva por la seguridad que padezcas.
ssh
Como uno va por la vida predicando el software libre no puedo más que recomendar el uso de OpenSSH que puede encontrarse en http://www.openssh.org. Si no me equivoco, esta herramienta es compatible con la norma ssh 1.x.
Para que cvs use este modo de autenticarse se pueden usar estas variables de entorno:
export CVSROOT=":ext:USUARIO@www.vivalavirgen.com:/home/cvs"
export CVS_RSH=/usr/bin/ssh
donde USUARIO es nuestro nombre de usuario; www.vivalavirgen.com es el servidor que aloja al repositorio; /home/cvs es el directorio del servidor en el que está el repositorio; /usr/bin/ssh es la ruta completa al amigo ssh.
Ten en cuenta que, usando esta técnica, tendrás que autenticarse (es decir, suministrar su contraseña) cada vez que ejecute cvs.
pserver
Esta técnica es más de «andar por casa». Se monta rápidamente y no necesita de programas añadidos.
export CVSROOT=":pserver:USUARIO@www.vivalavirgen.com:/home/cvs"
donde USUARIO es nuestro nombre de usuario; www.vivalavirgen.com es el servidor que aloja al repositorio; /home/cvs es el directorio del servidor en el que está el repositorio.
Si usa esta técnica, antes de poder trabajar con cvs debe autenticarse con el servidor. Eso se hace con la orden login:
$ cvs login
CVS le pedirá la contraseña del usuario que haya configurado. Si la contraseña es correcta cvs guardará la información que necesita en el fichero ~/.cvspass y no tendrá que volver a autenticarse. Si por algún motivo exotérico quisieras «cerrar la sesión cvs» bastará con hacer:
$ cvs logout
Modo de uso
A continuación se propone una sencilla metodología de trabajo con cvs para evitar trabajos redundantes. Piénsese por ejemplo en la eliminación de erratas o errores en documentos o en código fuente.
Antes de cada sesión de trabajo es conveniente hacer «cvs update» para asegurarnos de que disponemos de las últimas modificaciones.
Justo al acabar cada sesión de trabajo es conveniente hacer «cvs ci» para que todas nuestras modificaciones se propaguen en el servidor.
Bajar por primera vez el módulo
Para crear una copia de trabajo local del módulo cvs deseado debemos usar la orden co (o su equivalente checkout):
$ cd [padre-de-directorio-donde-se-alojará-el-módulo]
$ cvs co [nombre-del-módulo]
Esto creará una jerarquía de directorios donde se almacenará el módulo. Este paso sólo hay que hacerlo una vez por cada módulo. A partir de este momento no es necesario configurar las variables de entorno porque cvs sabe a qué repositorio pertenece el módulo con sólo examinar los subdirectorios CVS. No debes modificar nunca esos subdirectorios o volverás loco al pobre cvs.
Actualizar cambios
Cuando queramos actualizar la copia local del módulo con los cambios que hayan podido hacer otros usuarios y que ya han sido enviados al repositorio deberemos hacer:
$ cd [directorio-del-módulo]
$ cvs update
Observaréis que cvs informa acerca de qué está haciendo con cada fichero y para eso utiliza un código de una letra. Este es el significado:
U
Se ha bajado un fichero completamente nuevo.
P
Se ha bajado una modificación del fichero y ahora éste está actualizado.
A
El fichero lo ha añadido usted pero no lo ha subido todavía al repositorio.
R
El fichero ha sido borrado (pero podría recuperarse del histórico que se almacena en el repositorio).
C
Hay un conflicto: otra persona ha modificado antes las mismas partes del fichero y no le queda más remedio que revisarlo manualmente antes de poder subir los cambios de ese fichero.
M
Usted ha modificado el fichero pero aún no lo ha subido al repositorio.
?
CVS se ha encontrado un fichero que no tiene registrado y simplemente avisa y lo deja en paz.
Publicar nuestras modificaciones
Se usa la orden ci (o su equivalente commit):
$ cd [directorio-del-módulo]
$ cvs ci
Tras lo cual el sistema mostrará la pantalla de un editor de textos (el que tengamos configurado como nuestro favorito en las variables de entorno) para que describamos el cambio.
Resolución de conflictos
Tal cual la ONU habrá ocasiones en las que tengamos que resolver los conflictos que surjan entre diferentes versiones para que cvs continúe trabajando. Estos conflictos son normales y ocurren cuando dos o más personas modifican a la vez exactamente la mismas partes de un fichero. El procedimiento es simple:
- cvs se quejará de un fichero;
- editamos ese fichero;
- encontraremos unas marcas del tipo
[...]
>>>>>>>>>>>>>>
taca
===========
tacataca
<<<<<<<<<<<<<<
[...]
- El texto entre marcas es el que produce el conflicto. Hay que elegir qué modificación nos gusta y borramos todo lo demás.
- Si no quedan conflictos volvemos a hacer el «ci» y a ser felices.
Añadir ficheros al módulo
No olvides que cvs controlará sólo los ficheros que se le hayan indiciado expresamente. Cualquier otro fichero en el directorio cvs será ignorado.
Si quieres incluir un nuevo fichero o directorio al módulo cvs hay que seguir los siguientes pasos:
$ cd [directorio-del-módulo]
$ cvs add [fichero]
Pero si el fichero es binario hay que tener la precaución de hacer:
$ cd [directorio-del-módulo]
$ cvs add -kb [fichero]
¿Por qué?, se preguntará el lector más intrépido. Resulta que CVS usa varias variables (en realidad son de RCS, que funciona por debajo de CVS). Si el fichero es binario es posible que se de una combinación de bytes que coincidan con alguna de estas variables. Si así fuera, RCS/CVS modificaría el contenido y lo corrompería. También se debe a que el sistema de cálculo de diferencias que usan estos sistemas no está diseñado para trabajar con información binaria. Si se obra equivocadamente es probable que corrompamos los datos.
También quiero señalar que si bien se pueden gestionar ficheros binarios, no se hará control de versiones de los mismos. Sólo se guardará la última versión.
Tras el «add» hay que hacer un «cvs ci» para actualizar el servidor con los nuevos ficheros.
Eliminar ficheros del módulo cvs
Para eliminar un fichero del módulo cvs hay que hacer lo siguiente una vez borrado el fichero:
$ cd [directorio-del-módulo]
$ cvs remove [fichero]
En cambio, si queremos borrar físicamente los ficheros a la vez que los eliminamos del módulo deberemos usar:
$ cd [directorio-del-módulo]
$ cvs remove -f [fichero]
Cómo configurar un servidor y cómo subir nuevos módulos al repositorio. Si este documento es tu referencia favorita sobre cvs no merece la pena que te explique cómo se hacen estas cosas porque de todas formas no te ibas a enterar. Espabila un poco porque es bien fácil una vez que uno se ha mirado los manuales.
Si alguien con mejor corazón que yo tiene tiempo, que amplíe esta sección para beneficio de todos.
Herramientas con Interfaces gráficas para CVS
Espero que si llegas a este punto es por curiosidad o por la inercia de la lectura porque los verdaderos tecnófilos tienen que saber exactamente qué es lo que hacen en cada momento sin que ninguna clase de software intente facilitarles la vida encapsulando complejidades. Sobre todo cuando ese software ofrece interfaces gráficos.
En cualquier caso y para que nadie pueda dudar de mi profesionalidad voy a enumerar unos cuantos interfaces gráficos cvs en mayor o menor estado de desarrollo:
- pharmacy
- gcvs
- tkcvs, para unix y windows y que además saca un bonito grafo con el historial de desarrollo.
- Tortoise, para windows, integrado en el gestor de ficheros.
- wincvs, para windows, como bien sugiere el nombre. He oído hablar mal de él, pero como no lo he probado no puedo asegurar nada.
- cervisia, para KDE.
- lincvs
Bueno, ya me aburrí. para encontrar los enlaces que faltan, la mayoría aparecerán en http://freshmeat.net y con el tiempo tal vez aparezcan nuevas herramientas.
Otros recursos cvs interesantes
Otros recursos cvs más avanzados
- cvsadmin es una herramienta para administrar las cuentas de un repositorio.
- cvs-nserver es una reescritura y extensión de cvs para mejorar sus capacidades en red.
- cvsauth sirve para autenticar usuarios sin ejecutar en el servidor cvs como root.