Versión 0.1 - Marzo 26 de 2003
Copyright (c) 2003 Nelson Castillo.
arhuaco (en) yahoo.com
La última versión de este documento se mantendrá en http://www.geocities.com/arhuaco/doc/introcvs/
CVS es una herramienta realmente útil, aunque algo complicada. La motivación para escribir este documento es compartir los beneficios que estoy obteniendo al usar CVS con algunos de mis conocidos, y espero que con mis desconocidos también. No me considero experto en nada, así que las correcciones y contribuciones serán especialmente bienvenidas.
La idea es que usted pueda comenzar a utilizar CVS lo más rápido posible, esperando que con esto pueda ahorrar algún tiempo para leer los manuales de verdad. Si tiene un computador con GNU/Linux o una implementación similar de UNIX, es muy probable que ya tenga CVS instalado. En caso contrario, es probable que el contenido de este documento no sea de su interés. Existen tutoriales que indican como utilizar CVS en otros sistemas operativos. También existen herramientas gráficas que facilitan el uso de CVS. Acá haremos todo en la consola.
Supondré que está usando GNU/Linux y Bash. Si no, probablemente tendrá que cambiar una que otra cosa, sobre todo cuando se trata de manejar variables del entorno.
Esta es una versión preliminar e incompleta. Con su ayuda, podrá ser mejor.
De ninguna manera garantizo que la información aquí escrita está libre de errores ni que será inofensiva para su sistema. Es posible que su procesador implote al ejecutar alguna de las instrucciones aquí descritas. De igual forma, es posible que pierda por completo todos los respaldos de ese proyecto en el que ha estado trabajando durante cuatro años y que ya está por terminar. Proceda bajo su propia responsabilidad.
Este espacio está reservado para las personas que nos ayuden a mejorar este documento. Por el momento, un agradecimiento a todas las personas que escriben software libre por hacer de este un mundo mejor.
CVS es un sistema de control de versiones. Usarlo te permite llevar una historia de tus códigos fuentes, algo como una historia clínica. Existe una horda de programadores superdotados que hacen copias periódicas de los fuentes de sus programas, y que tienen la extraordinaria habilidad para recordar -detalladamente- cada cambio que hacen. Cuando se descubre un error (comportamiento inesperado) en uno de sus programas, inmediatamente recuerdan la línea exacta y el archivo del código fuente que fue cambiado y proceden a arreglar el error. De igual forma, utilizan una red telepática de banda ancha que sirve para intercomunicar a todos los desarrolladores y mantenerlos al tanto de las últimas versiones del código fuente de sus programas.
Este método tiene el inconveniente de desperdiciar espacio en disco. CVS guarda solamente los cambios entre las diferentes versiones de cada archivo. Además, la gran mayoría de los mortales no tenemos esa extraordinaria capacidad en nuestra RAM ni sabemos telepatía, y por eso es mejor usar CVS. Usar CVS no solo ayuda a quien programa, sino a todos aquellos que puedan estar interesados en seguir el progreso de un proyecto de programación, de un conjunto de páginas de Internet, o de un libro. En general, de un conjunto de archivos.
CVS permite que varios desarrolladores trabajen concurrentemente en el desarrollo de un mismo programa. De esa forma, se facilita el desarrollo de programas por parte de un equipo. Esto no es transparente, y de vez en cuando se presentarán problemas -conflictos- cuando dos desarrolladores realicen cambios en el mismo archivo. Afortunadamente, CVS le informará a un usuario si el archivo que desea actualizar ya ha sido modificado para que pueda arreglar los cambios que se han hecho.
Además de tener un registro detallado de los cambios que se hacen a cada archivo del proyecto, CVS guarda etiquetas que se pueden asociar a una liberación. Así que en un momento determinado podemos decir, ``CVS, por favor marca este código fuente con la versión 0-0-9''. Esto permite que en el futuro, se pueda decir algo como ``Hey CVS, páseme la versión 0-0-9 del proyecto'', o algo como ``Hey CVS, ¿qué cosas han cambiado entre la versión 0-0-9 de mi proyecto y el código fuente que tengo ahora?''.
CVS es software libre distribuido bajo la Licencia GNU, lo cual indica que en lugar de imponer restricciones sobre el uso que los usuarios le pueden dar a la herramienta, la licencia de CVS preserva las libertades de los usuarios y de la comunidad.
Tranquilo, no las guarda en /dev/null. Para eso existe un repositorio, que es una localización central en la que se guarda el código fuente de varios proyectos. El repositorio puede ser local (en tu disco duro), o remoto. Acá trabajaremos con repositorios locales y remotos, de la forma más simple posible. Por esto, utilizaremos el protocolo SSH para acceder a los archivos remotos.
Así que manos a la obra, creemos un repositorio en tu cuenta de usuario. Para el nombre de la raíz del repositorio, he escogido 'cvs', pero puedes escoger otro nombre. Además, recuerda que voy a escribir en el directorio $HOME/cvs, así que ten cuidado. Para indicarle a CVS la ubicación de tu repositorio, puedes utilizar la opción -d en cada llamada, o crear una variable en el entorno llamada $CVSROOT. Esta variable se puede crear en el entorno de tu usuario con:
$ export CVSROOT=$HOME/cvs
Suponiendo que usas Bash. Para verificar:
$ echo $CVSROOT /home/n/cvs
Mi usuario es 'n', por supuesto en tu máquina aparecerá otra cosa dependiendo de tu login.
Puedes incluir esta línea -el export- en tu archivo $HOME/.bash_profile o $HOME/.bashrc para que siempre que inicies una sesión, la variable esté disponible en el entorno. Si no te gusta la idea, entonces no olvides pasar la opción -d$HOME/cvs en cada invocación al comando cvs.
Ahora procedemos a inicializar el repositorio:
$ cvs init
si eres algo terco:
$ cvs -d$HOME/cvs init
Bueno, ahora te recomiendo revisar los archivos y directorios que se crean en tu repositorio.
$ ls -R $HOME/cvs /home/n/cvs: CVSROOT
/home/n/cvs/CVSROOT: checkoutlist config,v Emptydir modules,v taginfo checkoutlist,v cvswrappers history notify taginfo,v commitinfo cvswrappers,v loginfo notify,v val-tags commitinfo,v editinfo loginfo,v rcsinfo verifymsg config editinfo,v modules rcsinfo,v verifymsg,v
/home/n/cvs/CVSROOT/Emptydir:
Felicitaciones. Ya tienes un repositorio de CVS en tu disco duro local. Ahora, creemos un proyecto con el único propósito de ocupar espacio en nuestro recién creado repositorio. Usualmente, uno no maneja directamente los archivos que se encuentran dentro del repositorio sino que se invoca el programa cvs con diferentes opciones que permiten manipularlos.
Comenzaremos con un programa en C que cambiará el curso de la historia. Un programa en C que imprimirá la cadena ``Hola CVS''. El primer paso será crear un directorio en el que escribiremos el código fuente inicial.
$ mkdir hola $ cd hola
Ahora, utilizando tu editor favorito, puedes crear el código fuente. Primero, un archivo llamado hola.c:
----------------------------- #include <stdio.h> #include <stdlib.h>
int main (int argc, char *argv[]) { printf("Hola CVS");
return EXIT_SUCCESS; } -----------------------------
Luego el archivo Makefile, que tendrá las instrucciones para compilar nuestro programa. Es importante notar que los espacios que aparecen antes de gcc no son espacios, es un TAB.
----------------------------- # Makefile de ejemplo
hola: hola.c gcc hola.c -o hola -----------------------------
Este será el primer proyecto que ingresará a nuestro repositorio. Toda una obra de arte. Recuerda que los archivos Makefile y hola.c deben estar en el directorio recién creado.
$ ls hola.c Makefile
Ahora vienen las palabras mágicas, la invocación al programa cvs.
$ cvs import -m "Importando los fuentes" hola nelson inicio N hola/Makefile N hola/hola.c
No conflicts created by this import
¡Funcionó! Se han importado los fuentes. Recuerda que debes tener la variable de entorno $CVSROOT asignada o utilizar la opción -d.
Ahora expliquemos los detalles, primero la acción import indica que lo que queremos hacer es 'importar' un proyecto al repositorio. Usualmente esto se hace cuando se quiere crear el proyecto a partir de archivos existentes, que es nuestro caso. Luego la cadena que se escribió con la opción -m, permite especificar una descripción a nuestra acción. La idea es que esté presente en cada modificación que hagamos al repositorio, para que cuando alguien muestre el historial de un archivo, le sea más fácil averiguar cual fue la razón del cambio. Un ejemplo de valores posibles para ésta cadena son:
-m "arreglado el error que formateaba el disco duro" -m "cambié la llamada a gets por fgets" -m "ahora se imprime -Hola Mundo- en lugar de -Hola CVS-"
Esta cadena se utiliza con las demás acciones, no solo con import, y si no se especifica en la invocación, entonces se llamará a un editor para que se escriba esta descripción. El editor puede ser vim, emacs, nano o tu editor favorito. Puedes especificar tu editor preferido usando la variable del entorno $EDITOR. No deberías usar un editor gráfico, ya que no funcionaría en consola.
Bueno, no discutamos ahora si vim es mejor que emacs o lo contrario, mejor sigamos con las opciones de CVS. La palabra 'hola' indica el nombre del proyecto a crear. Este además será el nombre del directorio que contendrá el proyecto.
Luego tenemos la cadena 'nelson', que es una etiqueta que identifica al autor del proyecto (etiqueta del vendedor). No, no tiene que ser siempre nelson. Puedes cambiarla por tu nombre o el de tu empresa : esta la coloqué para hacerme publicidad.
Y finalmente, está la palabra 'inicio'. Esta es una marca que se pone a todos los fuentes del proyecto (etiqueta de la liberación). Recuerda que uno puede marcar el proyecto con diferentes etiquetas a través del tiempo para luego darle órdenes al CVS. Poner una nueva etiqueta no borra las anteriores.
Si eres lo suficientemente curioso, puedes ir a mirar tu repositorio local a ver que ha cambiado.
Ahora vamos a hacer modificaciones, con la protección de CVS. Voy a borrar el código fuente inicial, porque ya no lo necesitamos. En un proyecto de verdad sería buena idea guardar una copia de respaldo. Si, así como se hacía antes de CVS. ¿Tan rápido se te ha olvidado?
$ rm Makefile hola.c $ cd .. $ rmdir hola
Bueno, no te preocupes. Ya sabes que las fuentes de nuestro gran programa están en el repositorio local.
Ahora comenzamos a disfrutar de las ventajas de CVS. Vamos a crear una copia de trabajo, que será un directorio en el que estará una copia del proyecto que está en el repositorio. La idea es que cada desarrollador tenga su copia local y que vaya enviando los cambios al repositorio. No te desesperes. Un paso a la vez.
Para hacer cambios a los archivos que se encuentran en el repositorio de CVS se requiere tener permiso de escritura en el servidor. En la mayoría de los ejemplos de este documento trabajaremos con copias locales, así que esto no debe ser un problema.
$ cvs checkout hola cvs checkout: Updating hola U hola/Makefile U hola/hola.c
La acción checkout significa que se desea crear una copia local del proyecto 'hola'. Se crea entonces un directorio con el nombre del proyecto y dentro de él se crearan copias de los archivos y sub-directorios que hagan parte del mismo.
Ahora puedes entrar a la copia local de trabajo y ver que archivos tenemos.
$ cd hola $ ls CVS hola.c Makefile
Y otra vez tenemos acceso a nuestros archivos, hola.c y Makefile. Aparece un directorio CVS que es utilizado por el programa cvs, no hace parte del proyecto. Entonces, veamos que hace nuestro programa. Primero lo compilamos.
$ make gcc hola.c -o hola
Y luego lo corremos.
$ ./hola Hola CVS[n@localhost hola]$
¡Hey! ¡Mira lo que pasó! ¡El programa tiene errores! Le falta un caracter '\n' al final de la cadena que imprimimos. Bueno, ya podemos hacer una modificación crucial a nuestro programa. Editamos el archivo y hacemos el cambio. Ya lo edité. No te voy a decir como. ¿Que cómo vas a saber que cambié? No hay problema, para eso está la acción diff, que permite ver que cambios se han hecho en los fuentes.
$ cvs diff cvs diff: Diffing . Index: hola.c =================================== RCS file: /home/n/cvs/hola/hola.c,v retrieving revision 1.1.1.1 diff -r1.1.1.1 hola.c 7c7 < printf("Hola CVS"); --- > printf("Hola CVS\n");
Como puedes ver, he cambiado la línea
printf("Hola CVS");
por
printf("Hola CVS\n");
¡Impresionante!
Bueno, ya te habrás dado cuenta de que no todas las acciones llevan la opción -m, solo las que hacen cambios al repositorio.
Existe una mejor forma de realizar la opción diff, y es utilizando la opción -u.
$ cvs diff -u cvs diff: Diffing . Index: hola.c =================================== RCS file: /home/n/cvs/hola/hola.c,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 hola.c --- hola.c 26 Mar 2003 03:25:20 -0000 1.1.1.1 +++ hola.c 26 Mar 2003 03:54:36 -0000 @@ -4,7 +4,7 @@ int main (int argc, char *argv[]) { - printf("Hola CVS"); + printf("Hola CVS\n");
return EXIT_SUCCESS; }
Así se se imprime un poco más de texto y esto permite ver con mayor facilidad lo que se ha cambiado. Se utiliza un '+' para el código que se añade y un '-' para el código que se quita. La salida de cvs diff o de cvs diff -u es comprendida por el comando patch. Esto significa que es posible enviarle el texto aquí generado a alguien que tenga la versión anterior del proyecto 'hola', y el podrá utilizar el comando patch para convertir su código al nuevo. Por supuesto, con este código tan pequeño eso no tiene mucho sentido, pero los programas reales pueden ser más grandes, ¿cierto?. En todo caso, hablaremos de esto más tarde.
Es bueno que sepas que si alguna vez le envías un parche a alguien, deberías utilizar la opción -u! : se considera mala educación el no hacerlo. También existe el comando GNU diff por separado, como parte del paquete diffutils. Pero no te me distraigas, que estamos trabajando en el proyecto 'hola'.
Bueno, hemos mejorado en gran medida nuestro programa, pero los cambios que hemos hecho no están todavía en el repositorio. Así que si alguien hace un checkout en otro lado, tendrá la versión anterior de este programa. Guardemos los cambios.
$ cvs commit -m "agregué un final de línea" hola.c Checking in hola.c; /home/n/cvs/hola/hola.c,v <-- hola.c new revision: 1.2; previous revision: 1.1 done
Ya hemos escrito los cambios al repositorio utilizando la acción commit. Como ves, la salida del programa nos dice que ahora la revisión del archivo hola.c es la 1.2 y que antes era la 1.1. En la invocación a cvs especifiqué que se iban a guardar los cambios del archivo hola.c. Si se omite el listado de archivos que se quieren cambiar entonces se hará un commit de todos los archivos que se hayan cambiado! Esto es particularmente molesto, así que debes tener cuidado. Si te equivocas es posible que guardes cambios que no querías guardar o que el comentario que habías pensado para un solo archivo se guarde para todos los archivos que se hayan modificado. ¿Recuerdas la opción -m?
Si ahora hago un cvs diff, no habrán cambios para mostrar.
$ cvs diff -u cvs diff: Diffing .
Ahora, voy a hacer cambios... he he he...
$ cvs diff -u cvs diff: Diffing . Index: Makefile =================================== RCS file: /home/n/cvs/hola/Makefile,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 Makefile --- Makefile 26 Mar 2003 03:25:20 -0000 1.1.1.1 +++ Makefile 26 Mar 2003 04:44:43 -0000 @@ -2,3 +2,8 @@
hola: hola.c gcc hola.c -o hola + +.PHONY: clean + +clean: + rm hola Index: hola.c =================================== RCS file: /home/n/cvs/hola/hola.c,v retrieving revision 1.2 diff -u -r1.2 hola.c --- hola.c 26 Mar 2003 04:29:49 -0000 1.2 +++ hola.c 26 Mar 2003 04:43:29 -0000 @@ -4,7 +4,10 @@ int main (int argc, char *argv[]) { - printf("Hola CVS\n"); + printf("Este cambio no es deseado \n");
+ if(argc>1) + printf("%s\n", argv[1]); + return EXIT_SUCCESS; }
Bueno, he hecho cambios en los archivos Makefile y hola.c. Deseo guardar los cambios hechos en el primer archivo, pero no los que hice en el segundo. Ahora, ¿Quién podrá ayudarme? : update! Si hago cambios en un archivo y deseo volver a la copia que está en el repositorio, simplemente lo borro y hago un cvs update.
$ rm hola.c $ cvs update hola.c cvs update: warning: hola.c was lost U hola.c
Ahora hacemos un diff
$ cvs diff cvs diff: Diffing . Index: Makefile =================================== RCS file: /home/n/cvs/hola/Makefile,v retrieving revision 1.1.1.1 diff -r1.1.1.1 Makefile 4a5,9 > > .PHONY: clean > > clean: > rm hola
Ahora solamente quedan los cambios que hice en el archivo Makefile y puedo proceder a guardarlos.
$ cvs commit -m "adicioné el target clean" Makefile Checking in Makefile; /home/n/cvs/hola/Makefile,v <-- Makefile new revision: 1.2; previous revision: 1.1 done
Solamente guardé los cambios que me interesaban. Nuestra copia local está sincronizada con el repositorio. Ahora podemos borrar los fuentes locales y hacer un cvs checkout la próxima vez que vayamos a trabajar con el repositorio.
$ cvs diff cvs diff: Diffing .
Es conveniente notar que el texto ``cvs diff: Diffing .'' se escribe a stderr, mientras que las diferencias se imprimen a stdout. Esto nos permite enviar la salida del cvs diff a un archivo para luego revisarlo con un editor que tenga realce de sintaxis o para enviarle los cambios a alguien por email.
$make clean $ cvs diff -u > /tmp/hola.patch cvs diff: Diffing .
$ vim /tmp/hola.patch $ cat /tmp/hola.patch | mail -s "Los cambios que hice" [email protected] $ echo =\)
Vamos a añadir un archivo al proyecto, se llamará LEAME. Crearé el archivo con el comando cat. Si, puedes crearlo con un editor si quieres.
$ cat > LEAME << "EOF" > hola > > Este es un proyecto que cambiará el mundo. > > Chao. > EOF
Y listo. Ahora miremos que hay en el directorio del proyecto.
$ ls CVS hola.c LEAME Makefile
Apareció el LEAME. Hagamos un diff.
$ cvs diff * > /tmp/hola.patch cvs diff: I know nothing about LEAME cvs diff: warning: directory CVS specified in argument cvs diff: but CVS uses CVS for its own purposes; skipping CVS directory
Recuerda que estamos viendo lo que se imprime al error estándar (stderr). Mira que he utilizado un asterisco (*) y el programa cvs me ha regañado, traduciré : ``Loco, no sé nada del documento LEAME. Ese archivo no hace parte del proyecto''. Personalmente, suelo invocar al cvs de ésta manera con frecuencia, para ver si se me ha olvidado poner algo en el proyecto. Si acá aparecen archivos intermedios generados por LaTeX, gcc, o por cualquier otro programa de aplicación, entonces puedes ignorar los mensajes de error que estos producirán. Debes tener especial cuidado y añadir los que si hacen parte del proyecto, para que no se pierdan.
Entonces, agreguemos el archivo LEAME.
$ cvs add LEAME cvs add: scheduling file `LEAME' for addition cvs add: use 'cvs commit' to add this file permanently
El archivo se ha programado para adición. Hagamos el commit.
$ cvs commit -m "Las instrucciones" LEAME RCS file: /home/n/cvs/hola/LEAME,v done Checking in LEAME; /home/n/cvs/hola/LEAME,v <-- LEAME initial revision: 1.1 done
Se ha añadido el archivo.
Vamos a adicionar un directorio llamado doc con instrucciones de instalación.
$ mkdir doc $ cvs add doc Directory /home/n/cvs/hola/doc added to the repository
No es necesario hacer un commit, pero para que el directorio tenga sentido, deberíamos crear un archivo dentro de él.
$ cat > doc/INSTALAR << "EOF" > No debe hacer nada para instalar. > EOF
$ ls doc/ INSTALAR
Bueno, ahora vamos a adicionar el archivo INSTALAR
$ cvs add doc/INSTALAR cvs add: scheduling file `doc/INSTALAR' for addition cvs add: use 'cvs commit' to add this file permanently
$ cvs commit -m "instrucciones de instalación" doc/INSTALAR RCS file: /home/n/cvs/hola/doc/INSTALAR,v done Checking in doc/INSTALAR; /home/n/cvs/hola/doc/INSTALAR,v <-- INSTALAR initial revision: 1.1 done
Y listo. Ya tenemos un directorio nuevo que contiene un archivo. Si hacemos un diff, ya se revisa el contenido del directorio.
ADVERTENCIA:Recuerda que no se revisarán los archivos que no estén en el repositorio.
$ cvs diff cvs diff: Diffing . cvs diff: Diffing doc
Voy a modificar el archivo recién creado:
$ echo "Y cvs no es del todo fácil" >> doc/INSTALAR
Y ahora miremos que ha cambiado:
$ cvs diff -u cvs diff: Diffing . cvs diff: Diffing doc Index: doc/INSTALAR =================================== RCS file: /home/n/cvs/hola/doc/INSTALAR,v retrieving revision 1.1 diff -u -r1.1 INSTALAR --- doc/INSTALAR 26 Mar 2003 06:38:28 -0000 1.1 +++ doc/INSTALAR 26 Mar 2003 06:42:53 -0000 @@ -1 +1,2 @@ No debe hacer nada para instalar. +Y cvs no es del todo fácil
Sabes que ese archivo LEAME ya no me gusta. Vamos a borrarlo.
$ rm LEAME $ ls CVS doc hola.c Makefile
Ahora utilizamos cvs remove para borrar el archivo.
$ cvs remove LEAME cvs remove: scheduling `LEAME' for removal cvs remove: use 'cvs commit' to remove this file permanently
Para que quede completamente borrado, se bebe usar cvs commit.
$ cvs commit -m "Chao LEAME" LEAME Removing LEAME; /home/n/cvs/hola/LEAME,v <-- LEAME new revision: delete; previous revision: 1.1.1.1 done
Y ya hemos borrado el archivo.
Bueno, ese directorio doc nos ha dado como que muchos problemas. Vamos a borrarlo.
$ cvs remove doc cvs remove: Removing doc cvs remove: scheduling `doc/INSTALAR' for removal cvs remove: use 'cvs commit' to remove this file permanently
$ cvs commit -m "borrando dentro de doc" doc Removing doc/INSTALAR; /home/n/cvs/hola/doc/INSTALAR,v <-- INSTALAR new revision: delete; previous revision: 1.1 done
Ahora viene el borrado del directorio. Para esto simplemente hay que... hey... espera... en CVS no se borran los directorios. Para que los directorios que están vacíos no se vean la próxima vez que hagas un checkout, puedes agregar la opción -P. Esta opción hace que los directorios vacíos no aparezcan.
Buena pregunta. Puedes simplemente borrarla, pero lo mejor es indicarle primero al CVS que quieres abandonar tu copia local.
$ cd .. $ cvs release hola You have [0] altered files in this repository. Are you sure you want to release directory `hola': y
Al hacer un cvs release, CVS te advertirá si estas borrando un archivo con cambios a los que no has hecho commit, y también te advertirá si hay un archivo nuevo que no ha sido añadido al repositorio. Si además quieres borrar tu copia local, entonces puedes usar la opción -d.
$ cd .. $ cvs release -d hola You have [0] altered files in this repository. Are you sure you want to release (and delete) directory `hola': y
Si varios desarrolladores están trabajando con un mismo repositorio es posible que alguien haya hecho un cambio a uno de los archivos que están en tu copia local. Haremos una prueba para mostrar lo que puede pasar.
Primero, hacemos un checkout del proyecto en $HOME/prueba.
$ mkdir prueba && cd prueba $ cvs checkout -P hola cvs checkout: Updating hola U hola/Makefile U hola/hola.c cvs checkout: Updating hola/doc
Ahora dupliquemos nuestra copia local en otro directorio. Si, también pudimos haber hecho un checkout en otro lado.
$ cp hola hola1 -R $ ls hola hola1
Entremos a hola1, hagamos un cambio y hacemos un commit.
$ cd hola1
Hago el cambio con mi editor favorito...
$ cvs diff -u cvs diff: Diffing . Index: hola.c =================================== RCS file: /home/n/cvs/hola/hola.c,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 hola.c --- hola.c 26 Mar 2003 06:17:36 -0000 1.1.1.1 +++ hola.c 26 Mar 2003 09:08:22 -0000 @@ -5,6 +5,7 @@ main (int argc, char *argv[]) { printf("Hola CVS\n"); + printf("Hola de nuevo");
return EXIT_SUCCESS; }
y luego hago un commit.
$ cvs commit -m "Ahora digo hola dos veces" hola.c Checking in hola.c; /home/n/cvs/hola/hola.c,v <-- hola.c new revision: 1.2; previous revision: 1.1 done
Hemos actualizado el repositorio, pero hay una copia que tiene la versión anterior. Entonces podemos entrar al otro directorio y suponer que los cambios que acabamos de hacer fueron hechos por otra persona.
$ cd .. $ cd hola $ cvs status
cvs status: Examining . =================================== File: Makefile Status: Up-to-date
Working revision: 1.1.1.1 Wed Mar 26 06:17:36 2003 Repository revision: 1.1.1.1 /home/n/cvs/hola/Makefile,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)
=================================== File: hola.c Status: Needs Patch
Working revision: 1.1.1.1 Wed Mar 26 06:17:36 2003 Repository revision: 1.2 /home/n/cvs/hola/hola.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)
Como puedes ver, el archivo hola.c necesita un parche ('Needs Patch'). Entonces podríamos hacer una actualización del repositorio local, y se vería algo como esto:
$ # cvs update # no lo vamos a ejecutar... por eso el '#' al inicio cvs update: Updating. U hola.c
Pero como eso sería muy fácil, vamos a hacer las cosas más interesantes y vamos a hacerle una modificación inocua al Makefile y un cambio local al archivo hola.c, que ya ha cambiado en el repositorio.
Cambiando... Listo. Veamos lo que hice:
$ cvs diff -u cvs diff: Diffing . Index: Makefile =================================== RCS file: /home/n/cvs/hola/Makefile,v retrieving revision 1.3 diff -u -r1.3 Makefile --- Makefile 26 Mar 2003 09:57:59 -0000 1.3 +++ Makefile 26 Mar 2003 09:58:17 -0000 @@ -1,4 +1,5 @@ # Makefile de ejemplo +# un comentario tonto
hola: hola.c gcc hola.c -o hola Index: hola.c =================================== RCS file: /home/n/cvs/hola/hola.c,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 hola.c --- hola.c 26 Mar 2003 06:17:36 -0000 1.1.1.1 +++ hola.c 26 Mar 2003 09:22:24 -0000 @@ -5,6 +5,7 @@ main (int argc, char *argv[]) { printf("Hola CVS\n"); + printf("Imprimo otra cosa. Lero lero.");
return EXIT_SUCCESS; }
Así que ahora se puede ver como están las cosas:
$ cvs status cvs status: Examining . =================================== File: Makefile Status: Locally Modified
Working revision: 1.1.1.1 Wed Mar 26 06:17:36 2003 Repository revision: 1.1.1.1 /home/n/cvs/hola/Makefile,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)
=================================== File: hola.c Status: Needs Merge
Working revision: 1.1.1.1 Wed Mar 26 06:17:36 2003 Repository revision: 1.2 /home/n/cvs/hola/hola.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)
Puedes utilizar GNU grep para filtrar la salida de cvs status. Como me da pereza leer sobre la sintaxis de las expresiones regulares de grep, usaré el programa perl.
$ cvs status | perl -nwe 'print if(/Merge|Modified|Patch|conflicts/)' cvs status: Examining . File: Makefile Status: Locally Modified File: hola.c Status: Needs Merge
Recuerda que la primera línea va a stderr, por eso no se filtró. Bueno, no comencemos con discusiones religiosas sobre SED y AWK versus Perl. Siempre lo mismo contigo. Que cosa.
Resumiendo, los estados que hemos visto para los archivos son:
Una forma más o menos fácil puede ser mover la copia local del archivo a otro lado, luego hacer un update para ver lo que cambió la otra persona, y luego hacer de nuevo las modificaciones que inicialmente queríamos hacer. Aunque esto suena complicado. Probemos mezclando lo que hizo la otra persona (Hipotética) con lo que hemos hecho, y resolvemos los conflictos a mano.
$ cvs update hola.c RCS file: /home/n/cvs/hola/hola.c,v retrieving revision 1.1.1.1 retrieving revision 1.2 Merging differences between 1.1.1.1 and 1.2 into hola.c rcsmerge: warning: conflicts during merge cvs update: conflicts found in hola.c C hola.c
Hubo conflictos en el merge porque modificamos líneas comunes, por lo que debemos leer y arreglar a mano. Si todo suena muy complicado, siempre podemos volver a la versión anterior de un archivo. Puede ser recomendable hacer una copia de lo que queríamos hacer antes de llamar a cvs update. Si alguien hace ahora un checkout del proyecto mientras estamos resolviendo el conflicto, él no se va a enterar. Pero es bueno que hagamos el commit rápido para que los demás puedan hacer un update o un merge lo más pronto posible.
Si nosotros hacemos un cvs status, veremos entre las líneas de la salida una línea en particular que dice:
File: hola.c Status: File had conflicts on merge
Los demás no se darán cuenta. Usualmente, una lista de discusión es muy útil en combinación con el CVS, ya que así los programadores pueden avisar cuando tienen intenciones de hacer un cambio en un archivo que es mantenido por varias personas. CVS no define las políticas que el grupo de desarrollo implementa.
Observa como ve el archivo hola.c la persona que hizo el merge. Es decir, como lo vemos nosotros. No se te olvide, estamos metidos en este problema.
-----------------------------
#include <stdio.h> #include <stdlib.h>
int main (int argc, char *argv[]) { printf("Hola CVS\n"); <<<<<<< hola.c printf("Imprimo otra cosa. Lero lero."); ======= printf("Hola de nuevo"); >>>>>>> 1.2
return EXIT_SUCCESS; } -----------------------------
Bueno, es hora de arreglar las diferencias con el programador hipotético. Resolvamos el conflicto, separado por '======='. Como Las acciones no son exclusivas ni se estorban, podemos dejar el programa de la siguiente manera:
-----------------------------
#include <stdio.h> #include <stdlib.h>
int main (int argc, char *argv[]) { printf("Hola CVS\n"); printf("Imprimo otra cosa. Lero lero."); printf("Hola de nuevo");
return EXIT_SUCCESS; } -----------------------------
Y luego podemos hacer commit, y nadie tiene por qué enterarse del problema. De hecho, no hay problemas... ¿Quién ha visto uno?
$ cvs commit -m "Imprimo lero lero" hola.c Checking in hola.c; /home/n/cvs/hola/hola.c,v <-- hola.c new revision: 1.3; previous revision: 1.2 done
Para complementar, veamos una prueba en la que no se presentan conflictos.
$ cvs update hola.c RCS file: /home/n/cvs/hola/hola.c,v retrieving revision 1.2 retrieving revision 1.3 Merging differences between 1.2 and 1.3 into hola.c M hola.c
Ahora el estado del archivo es:
File: hola.c Status: Locally Modified
Working revision: 1.3 Result of merge Repository revision: 1.3 /home/n/cvs/hola/hola.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)
Y podemos ver el diff:
$ cvs diff cvs diff: Diffing . Index: hola.c =================================== RCS file: /home/n/cvs/hola/hola.c,v retrieving revision 1.3 diff -r1.3 hola.c 3a4,5 > /* TODO: hacer algo mejor */ > 9a12 > printf ("Hola otra vez"); cvs diff: Diffing doc
Para aclarar, el comentario 'TODO: hacer algo mejor' fue agregado por el usuario hipotético. El cambio 'printf (``Hola otra vez'');' lo hice yo. Como vez, el diff es un poco difícil de entender. Solo quise mostrarte por qué hay que usar la opción -u. En todo caso, si nada se ha dañado podemos hacer commit.
$ cvs commit -m "imprimo más cosas" hola.c Checking in hola.c; /home/n/cvs/hola/hola.c,v <-- hola.c new revision: 1.4; previous revision: 1.3 done
Todos felices y comemos perdices.
Ha llegado la hora de mostrar nuestro trabajo al mundo. Para esto podemos exportar el contenido del repositorio. La diferencia entre cvs export y cvs checkout es que cvs export crea una copia del repositorio, lista para ser entregada al público, sin directorios auxiliares.
$ mkdir prueba $ cd prueba $ cvs export -D "1 seconds ago" hola cvs export: Updating hola U hola/Makefile U hola/hola.c cvs export: Updating hola/doc
$ls hola hola.c Makefile
Como ven, el contenido del directorio 'hola' no incluye los directorios que están vacíos. Este último efecto se puede conseguir con un cvs checkout si se le pasa la opción -P. Como ejemplo hemos exportado el código que estaba en el repositorio hace un segundo. Se puede exportar el que estaba la semana pasada, hace un mes, etc. O se puede utilizar una etiqueta:
$ cvs export -r 'inicio' hola
En este caso se usaría la etiqueta 'inicio'.
Ahora ya puedes:
$ tar cvf hola.tar hola hola/ hola/Makefile hola/hola.c $ gzip -9 hola.tar
Y si compila, ya lo podemos liberar =)
Cuando los proyectos crecen, van creciendo en tamaño y por lo tanto cada liberación ocupará más bytes. Larry Wall (el creador de Perl) escribió la versión original del programa patch. Como dije antes, el programa patch puede entender la salida de diff o de diff -u.
Ya te imaginarás para qué sirve todo esto. Si uno ya ha bajado un programa y para la siguiente versión del programa el autor publica los parches, entonces no es necesario bajar todo el programa, solo se bajan los parches. ¿Así que tu bajas la kernel de Linux siempre? Con razón se pone lento el servidor... Bueno, ya sabes que de ahora en adelante debes bajar los parches.
Si uno no espera que un parche sea leído detalladamente por humanos, está bien no usar -u para ahorrar espacio. De lo contrario, siempre se debería usar la opción -u.
Ya sabes como usar cvs export. Ahora hagamos un parche. Mira que usaremos la etiqueta 'inicio', que fue usada para crear el repositorio al comienzo de este documento.
Entonces, hagamos lo siguiente:
¿Recuerdas que te conté que podías tener cualquier versión? No te estaba mintiendo. Vamos a obtener el código inicial.
$ mkdir $HOME/test1 $ cd $HOME/test1
$ cvs checkout -P -r inicio hola cvs checkout: Updating hola U hola/LEAME U hola/Makefile U hola/hola.c cvs checkout: Updating hola/doc
¡Hey, que pasa! ¡ LEAME no estaba en la liberación inicial! Bueno, lo que pasó fue que en algún momento cometí un error y entonces decidí borrar el repositorio y crearlo de nuevo con unos fuentes que ya tenían el archivo LEAME. Así que en esta sección, esos son los fuentes iniciales. Si, estoy apenado contigo.
Veamos un tree del directorio hola:
$ tree hola hola 1-- CVS 1 1-- Entries 1 1-- Repository 1 1-- Root 1 `-- Tag 1-- LEAME 1-- Makefile `-- hola.c
Podemos ver la historia de los cambios realizados a los archivos del código fuente:
$ cd hola $ cvs log LEAME
RCS file: /home/n/cvs/hola/Attic/LEAME,v Working file: LEAME head: 1.2 branch: locks: strict access list: symbolic names: inicio: 1.1.1.1 nelson: 1.1.1 keyword substitution: kv total revisions: 3; selected revisions: 3 description: ---------------------------- revision 1.2 date: 2003/03/26 06:49:42; author: n; state: dead; lines: +0 -0 Chao LEAME ---------------------------- revision 1.1 date: 2003/03/26 06:17:36; author: n; state: Exp; branches: 1.1.1; Initial revision ---------------------------- revision 1.1.1.1 date: 2003/03/26 06:17:36; author: n; state: Exp; lines: +0 -0 nuevas fuentes viejas
Aquí se ve la historia del archivo LEAME, que al final fue borrado. El nombre del archivo es opcional, si no se especifica, se muestra la historia de los archivos del proyecto. No te me distraigas, estamos haciendo parches.
Ahora, vamos a generar un parche que actualice la versión inicial a la versión actual. Para eso voy a hacer lo siguiente:
$ cvs .. $ mv hola hola_inicial $ cvs checkout -P hola cvs checkout: Updating hola U hola/Makefile U hola/hola.c cvs checkout: Updating hola/doc
Hemos obtenido la versión actual.
$ tree hola hola 1-- CVS 1 1-- Entries 1 1-- Repository 1 `-- Root 1-- Makefile `-- hola.c
Y no aparece el infame archivo LEAME. Vamos a hacer un parche para que los usuarios que tienen la liberación inicial de nuestro programa puedan actualizarse sin problemas. Bueno, imaginemos que ese es el caso.
$ cd hola $ cvs rdiff -r 'inicio' hola > ../hola-inicio_final.patch cvs rdiff: Diffing hola cvs rdiff: Diffing hola/doc
Hemos generado el parche que convierte los fuentes de la distribución marcada 'inicio' con los de la distribución... que vamos a marcar como final.
$ cvs tag -c final . cvs tag: Tagging . T Makefile T hola.c
La opción -c es útil debido a que la etiqueta se coloca al al código que está en el repositorio, y no al código local. Esta opción verifica que el código de la copia local esté al día con el del repositorio antes de colocar la etiqueta. ¡No te distraigas! Estamos haciendo un parche.
Seguimos con el parche... el parche que creamos está en el archivo ../hola-inicio_final.patch, vamos a leerlo.
Index: hola/LEAME diff -c hola/LEAME:1.1.1.1 hola/LEAME:removed *** hola/LEAME:1.1.1.1 Wed Mar 26 01:17:36 2003 --- hola/LEAME Wed Mar 26 07:16:59 2003 *************** *** 1,5 **** - hola - - Este es un proyecto que cambiará el mundo. - - Chao. --- 0 ---- Index: hola/Makefile diff -c hola/Makefile:1.1.1.1 hola/Makefile:1.4 *** hola/Makefile:1.1.1.1 Wed Mar 26 01:17:36 2003 --- hola/Makefile Wed Mar 26 05:41:34 2003 *************** *** 1,4 **** --- 1,5 ---- # Makefile de ejemplo + # un comentario tonto
hola: hola.c gcc hola.c -o hola Index: hola/hola.c diff -c hola/hola.c:1.1.1.1 hola/hola.c:1.4 *** hola/hola.c:1.1.1.1 Wed Mar 26 01:17:36 2003 --- hola/hola.c Wed Mar 26 05:50:19 2003 *************** *** 1,10 **** --- 1,15 ---- #include <stdio.h> #include <stdlib.h>
+ /* TODO: hacer algo mejor */ + int main (int argc, char *argv[]) { printf("Hola CVS\n"); + printf("Imprimo otra cosa. Lero lero."); + printf("Hola de nuevo"); + printf("Hola otra vez");
return EXIT_SUCCESS; }
Y ahora lo aplicamos al código que está en el directorio hola_inicial.
$ cd .. $ cd hola_inicial $ cat ../hola-inicio_final.patch | patch patching file LEAME patching file Makefile patching file hola.c
Y listo. El parche se ha aplicado. Ten en cuenta que en el directorio hola_inicial hay un checkout, ¡no un export! El archivo LEAME ha quedado, pero ahora está vacío. Si miras la página del manual de patch notarás que existe una opción --remove-empty-files, que borra los archivos que quedan vacíos después de aplicar el parche. Puedes comprobar que las versiones ahora son iguales con un diff. Mira que es la primera vez que uso el programa diff, ¡que es diferente de cvs diff!
$pwd /home/n/test1/hola_inicial $ diff hola.c ../hola/hola.c $ diff Makefile ../hola/Makefile $ cat LEAME
diff no muestra diferencias entre los archivos, porque no hay diferencias. Nuestro parche los ha hecho iguales.
Ahora queda de tu parte mirar el manual para ver como se pueden hallar las diferencias entre dos etiquetas. Esta bien, una pista:
$ cvs rdiff -r 'inicio' -r 'final' hola
debería producir un parche igual al que usamos en este ejemplo.
$ cvs rdiff -r 'final' -r 'inicio' hola
La última línea crea un parche (por lo menos hace el diff) que convierte la versión etiquetada 'final' a la etiquetada 'inicio'. No consumas bebidas con alcohol antes de crear o aplicar parches.
Un repositorio CVS puede estar en una máquina local, o en un servidor remoto mientras puedas conectarte a él con SSH (shell seguro). Existen otras formas de conectarse a un servidor CVS por red. Consideraremos la forma más simple por el momento.
Primero, debes decirle al programa cvs que deseas usar el programa ssh para establecer la conexión remota. Por estos días, casi nadie utiliza rsh en servidores conectados a Internet porque es inseguro, así que utilizaremos el programa ssh. Además, puedes especificar de una vez el $CVSROOT, para que no tengas que utilizar la opción -d en cada llamada a cvs.
$ export CVS_RSH=ssh $ export CVSROOT=:ext:[email protected]:/home/n/cvs
Puedes añadir esto a tu $HOME/.bash_profile. En la siguiente invocación, debes reemplazar n por tu login en la servidor remoto, y 127.0.0.1 por la IP o el nombre del servidor en cuestión. He tomado estos valores como ejemplo, y haré una conexión por ssh a una dirección IP local.
$ cvs checkout hola [email protected]'s password: cvs server: Updating hola U hola/Makefile U hola/hola.c cvs server: Updating hola/doc
Ahora, hagamos alguna operación:
$ cd hola/ $ cvs diff [email protected]'s password: cvs server: Diffing . cvs server: Diffing doc
Bueno, un pequeño inconveniente es que debes escribir la contraseña del servidor para cada operación que realices. Esto tiene solución, es posible generar una llave en el servidor remoto y tenerla también en la máquina local, para que puedas hacer login con ssh sin necesidad de escribir el password. Esto tiene sus riesgos, así que solamente te diré que es posible hacerlo. ¿Se debería añadir esto a la documentación?
En esta sección se escribirán brevemente cosas que están por fuera del alcance de este documento introductorio. Entre otras cosas, por que no soy todavía competente en ellas. Eso de 'Avanzado' siempre es relativo.
Una vez que has guardado los cambios de un grupo de archivos, CVS se olvida de este cambio como un todo. La bitácora que se lleva es sobre cada archivo del repositorio.
No es necesario guardar todos los archivos de un programa. Puede ser un desperdicio muy grande -por no nombrar las molestias- guardar archivos que sean generados automáticamente por el mismo programa, o por programas que deben estar en la plataforma de destino. Esto suele pasar cuando se está utilizando Autoconf. Se puede ejecutar un ``make distclean'' antes de importar los fuentes al repositorio, ya que muchas cosas se generan cuando invocas el 'configure'. Tu sabrás que hacer.
Como hemos visto en el documento, puedes utilizar el archivo $HOME/.bash_profile para guardar algunas preferencias. Mira la documentación de CVS si quieres saber más acerca de las variables del entorno que afectan su comportamiento.
También puedes crear un archivo $HOME/.cvsrc que guarde algunas preferencias acerca del uso del porgrama cvs.
Ejemplo del archivo $HOME/.cvsrc:
diff -u update -P checkout -P
Esto hará que la opción que aparece al frente de cada acción se utilice siempre que se ejecute el programa cvs con la acción correspondiente. Así no olvidarás hacer los diffs con la opción -u, si ejecutas algo como:
$ cvs diff | mail -s "cambios" [email protected]
Es posible bifurcar las versiones de las fuentes de un programa en diferentes ramas de desarrollo. De esa forma, los autores pueden trabajar sobre diferentes versiones del software. Esto no lo he usado. Puede servir para corregir algunos errores en una versión anterior de un programa para el que se quieren proveer actualizaciones. Por ejemplo, un parche que corrige problemas de seguridad. También se puede usar para que un programador haga un desarrollo experimental en una parte del código, que estará alojado en el mismo repositorio.
Bueno, hay veces que es útil importar archivos binarios al repositorio. Por ejemplo, imágenes de una página WEB. Acá haremos algo muy horrible con el único propósito de mostrar que si se puede.
$ cvs checkout hola $ cd hola $ make gcc hola.c -o hola
$ cvs add -kb hola cvs add: scheduling file `hola' for addition cvs add: use 'cvs commit' to add this file permanently
$ cvs commit -m "que feo" hola RCS file: /home/n/cvs/hola/hola,v done Checking in hola; /home/n/cvs/hola/hola,v <-- hola initial revision: 1.1 done
$ rm hola $ cvs update hola cvs update: warning: hola was lost U hola
$ ./hola Hola CVS Imprimo otra cosa. Lero lero.Hola de nuevoHola otra vez
La clave : invocamos el cvs add con la opción -kb. ¿Como se verá un cvs diff de un binario? No, no salen cosas graciosas en la consola. Después de hacer cambios... :
$ cvs diff hola Index: hola ============================ RCS file: /home/n/cvs/hola/hola,v retrieving revision 1.1 diff -r1.1 hola Binary files /tmp/cvswUeSBR and hola differ
Puedes hacer backups del repositorio. CVS no tiene forma de detectar si algunos archivos del repositorio se han dañado. Esto es grave, pero igual esto podría afectar tus fuentes normales. Averigua un poco más y actualiza ésta sección.
Al utilizar CVS se pierde el miedo de probar nuevas cosas, ya que casi siempre se puede volver atrás y es más fácil tener control sobre el proyecto que se está realizando. Hay que pagar un precio : aprender a utilizar la herramienta. Como es probable que de vez en cuando se cometan errores graves, las copias de respaldo son la primera línea de defensa.
Al principio, será un poco tedioso usar CVS, pero en unas horas te preguntarás cómo pudiste vivir tanto tiempo sin usarlo. CVS no es un sustituto de buenas prácticas de programación y mucho menos del correcto manejo de un proyecto. Es solamente una buena ayuda.
Ahora ve donde tu amigo o amiga, y pregunta irónicamente: ¿Ya sabes usar CVS?
http://www.cvshome.org/ : La casa de CVS en Internet / Manual en línea.
man cvs : Página del Manual
info cvs : Documentación en Texinfo. Muy buena.