====== Master of the UOC (Universidad Oberta de Catalunya) ====== ===== Reconfigurar teclado en linux ===== Con el teclado del sistema Toshiba 430CDT que estamos manejando se dan unas particularidades que lo hacen peculiar: - La tecla "AltGr" no está. En el lugar donde debería estar, encontramos la tecla /o/a (primero, primera) - Las teclas de mayor que y menor que, tampoco están - La tecla de slash invertido tampoco está Era preciso resolver estas carencias para que el teclado funcionara correctamente. A fin de lograrlo con éxito, fué preciso identificar cómo funciona el teclado en Debian. ==== Funcionamiento del teclado en Debian ==== Cuando se arranca el sistema, el script ''/etc/init.d/keymap.sh'' es el encargado de cargar la configuración del teclado, que se encuentra en ''/etc/console/boottime.kmap.gz''. Si es la primera vez que se ejecuta (porque se acaba de reinstalar el sistema operativo, por ejemplo), es posible que esa lectura del mapa de teclado se efectúe de ''/var/state/misc/kernel.kmap''. En el momento actual, la inspección del script indica que esto no es así ya que la ruta a ese archivo aparece comentada. ===== Los mapas de teclado en Linux ===== Linux posee un potente mecanismo para asignar las teclas físicas del teclado a teclas "logicas", que son las que los programas reciben. Toda esta parafernalia se consigue con tres elementos: * los mapas de teclado, ficheros ascii donde se asigna una tecla física (o combinación de varias, como Alt + F1) a un caracter o varios caracteres * el fichero loadkeys, encargado de "leer" esos mapas de teclado y guardarlos en el kernel * el comando showkeys, que al pulsar una tecla devuelve el número de tecla física que tiene ===== Reprogramando el teclado ===== Para reprogramar el teclado, lo primero que hicimos estudiar cómo íbamos a colocar las teclas: * por su proximidad, la tecla /o/a (masculine, ordfeminine, slash) sería la tecla AltGr. * los caracteres /o/a se colocarían como AltGr+8 y AltGr+9 * el caracter backslash se colocaría en AltGr+7 Mediante el comando showkeys identificamos la tecla /o/a como de código 41. Su programación actual es: keycode 41 = masculine ordfeminine backslash nul alt keycode 41 = Meta_grave y será sustituida por: keycode 41 = AltGr # keycode 41 = masculine ordfeminine backslash nul # alt keycode 41 = Meta_grave El siguiente paso es identificar la tecla 7/slash. Mediante showkey identificamos que se trata de la tecla de código 8, que en el mapa de teclado actual (/etc/console/boottime.kmap.gz) figura como: keycode 8 = seven slash braceleft Control_underscore alt keycode 8 = Meta_seven y al tercer valor que es el valor que tiene la tecla cuando se pulsa AltGr, lo reemplazamos por backslash: keycode 8 = seven slash backslash Control_underscore En el caso de las teclas 9 y 10, que corresponden a los caracteres 8 y 9 respectivamente, se efectúa la misma operación para que retornen "masculine" y "ordfeminine" respectivamente: keycode 9 = eight parenleft masculine Delete alt keycode 9 = Meta_eight keycode 10 = nine parenright ordfeminine alt keycode 10 = Meta_nine Queda ahora colocar los caracteres mayor que y menor que. Por proximidad, se colocarán en las teclas AltGr+Z y AltGr+X. Donde antes estaba: keycode 44 = z keycode 45 = x ahora quedará como: keycode 44 = z altgr keycode 44 = less keycode 45 = x altgr keycode 45 = greater ====== Actualización del sistema ====== Para actualizar el sistema el primer paso que hemos realizado ha sido añadir la siguiente línea en ''/etc/apt/sources.list'': deb ftp://ftp.rediris.es/sites/ftp.es.debian.org/debian oldstable main contrib non-free Y ejecutar el comando ''apt-get update'' para que actualice el repositorio de paquetes. Hemos de hacer notar que woody ha pasado de ser "stable" a "oldstable" en los repositorios de paquetes de debian. A continuación hemos ejecutado "apt-get upgrade"[1] para que actualice aquellos paquetes que proceda. ====== Otras modificaciones en el sistema operativo que no se han descrito ====== ==== Corrección de la configuración del lector de noticias ==== El lector de noticias inn tenía un error en el fichero de configuración ''/etc/news/inn.conf'' relativo al nombre del dominio y al nombre del host, que no habían sido configurados correctamente. Se modificó el fichero para que el contenido: # General Settings domain: innflags: mailcmd: /usr/lib/news/bin/innmail server: localhost Quedara así: # General Settings domain: raulluna.net innflags: mailcmd: /usr/lib/news/bin/innmail server: tas Este error provocaba un mensaje de error que llegaba por correo electrónico cada vez que se ejecutaba el crontab ''/etc/cron.d/inn2''. ==== Ajuste de permisos para el acceso a unidad de cdrom y a floppy disk ==== La configuración de permisos para el acceso al cdrom y al floppy disk eran poco operativos, además de inseguros. Los permisos iniciales eran rwxr-xr-x para el usuario root y el grupo root, lo que implica que en la práctica sólo el superusuario tiene acceso de lectura y escritura a estos dispositivos, y que todos los usuarios del sistema tienen acceso a los mismos una vez montados: chupete:/# ls -lad floppy cdrom drwxr-xr-x 2 root root 4096 Nov 19 2004 cdrom drwxr-xr-x 3 root root 1024 Jul 23 18:58 floppy Resulta más adecuado hacer un uso racional de los grupos "cdrom" y "floppy" de tal modo que sólo los usuarios que pertenezcan a estos grupos tengan acceso a estos dispositivos por defecto. Para ello se configuró el grupo propietario de estos directorios como "cdrom" y "floppy" según el caso, y los permisos se establecieron como rwxrwx---, de tal modo que para acceder al floppy o al cdrom se tiene que pertenecer a estos grupos: chupete:/# ls -lad floppy cdrom drwxrwx--- 2 root cdrom 4096 Nov 19 2004 cdrom drwxrwx--- 3 root floppy 1024 Jul 23 18:58 floppy ==== Instalación de paquetes complementarios ==== Se han instalado los siguientes paquetes complementarios: bzip2 ==== Corrección de problemas con apt ==== Se ha resuelto un problema con la configuración de apt: la línea que había en sources.list para el acceso a security era: deb http://security.debian.org/ stable/updates main Cuando debian pasó "sarge" a nueva versión "stable" y "woody" pasó a "oldstable" se produjo un problema con esto, ya que empezó a descargar paquetes de versiones posteriores a las que funcionaban en woody, creando colisiones. Bastó corregirla línea en cuestión: deb http://security.debian.org/ oldstable/updates main Y ejecutar apt-get update / apt-get upgrade para que todo volviera a funcionar con normalidad. ====== Instalación de Programas de Control de Código Fuente ====== ==== Qué se va a evaluar ==== **Portabilidad** En esta sección analizaremos la disponibilidad de esta herramienta para otras plataformas distintas de Linux; tanto de las herramientas cliente, para la descarga y desarrollo en Windows, pero trabajando con un servidor Linux, como en las herramientas servidor, para trabajar desde cualquier plataforma a un servidor en cualquier plataforma. **Realizando el primer cambio** La primera prueba que se va a realizar es completar un cambio simple mediante la herramienta de control de código fuente; el objetivo es familiarizarse con la herramienta, comprobar su facilidad/dificultad de uso; si es fácil implantarla en un proyecto y si la curva de aprendizaje de la misma es alta. **Realizando un cambio concurrente** Un aspecto que nos ha parecido interesante evaluar es el tratamiento que hace el sistema de control de código fuente de varios cambios que se hagan en el repositorio en la misma línea de código; y en general determinar que los cambios de unos usuarios no resultan destruidos por los cambios de otros usuarios. El espíritu de esta prueba es también evaluar si es posible o no acceder al repositorio remotamente; en caso de que sí fuera posible, comprobar cuál es el proceso para realizarlo y ver las respuestas que se producen con este modo de acceso. ==== OpenCM ==== chupete:~/opencm-0.1.2alpha8/base# ./configure --prefix /tmp/opencm --with-ssl-dir=/usr/lib OpenCM dice que no tiene instalada la librería openSSL a pesar de que sí está instalada: chupete:~/opencm-0.1.2alpha8/base# apt-get install openssl Reading Package Lists... Done Building Dependency Tree... Done Sorry, openssl is already the newest version. 0 packages upgraded, 0 newly installed, 0 to remove and 1 not upgraded. chupete:~/opencm-0.1.2alpha8/base# ==== GNU arch ==== GNU arch parece que adolece del mismo problema: la comprobación de configure dice que no está instalado "diff3": chupete:~/tla-1.3/=build# ../src/configure --prefix /tmp/gnu_arch/ --with-gnu-diff3 /usr/bin/diff3 The configured versions of diff and diff3 do not handle files not ending in newline correctly. configured diff = diff. configured diff3 = /usr/bin/diff3. Use config options --with-gnu-diff PROG --with-gnu-diff3 PROG NOTE: especially on BSD-derived systems, you will want to grab a recent version of GNU diff and compile it for use with those config options. You don't need to replace the native diff tools on your system, but you do need to configure tla specially. Sorry about that -- some BSDs have made a poor decision about their native diffs. (Example systems: some NetBSD, FreeBSD, and MacOS versions.) Sin embargo, sí que lo está: chupete:~/tla-1.3/=build# apt-cache search diff3 diff - File comparison utilities chupete:~/tla-1.3/=build# apt-get install diff Reading Package Lists... Done Building Dependency Tree... Done Sorry, diff is already the newest version. 0 packages upgraded, 0 newly installed, 0 to remove and 1 not upgraded. chupete:~/tla-1.3/=build# diff diff diff3 chupete:~/tla-1.3/=build# diff3 diff3: missing operand diff3: Try 'Diff3 --help' for more information. Con los paquetes *.deb que encontré tampoco funciona: informa que la versión de libc no es la que el paquete esperaba y cancela. ==== darcs ==== === Qué dice su página web === [1] http://abridgegame.org/darcs/ Su página web[1] presenta a darcs como "otro sistema de control de código fuente", presentando como gran ventaja que es un sistema descentralizado. Como base teórica el autor presenta una "teoría de los parches", con raices en la mecánica cuántica, que sirve de fundamento teórico del software. === Instalación === Según su página web, tras modificar el fichero sources.list de debian para que incluya los fuentes al proyecto: deb http://abridgegame.org/debian woody/ Hemos ejecutado ''apt-get update'' para que actualice los repositorios de paquetes: chupete:/etc/apt# apt-get update Hit http://security.debian.org stable/updates/main Packages Hit http://security.debian.org stable/updates/main Release Get:1 http://abridgegame.org woody/ Packages [1100B] Ign http://abridgegame.org woody/ Release Fetched 1100B in 1s (763B/s) Reading Package Lists... Done Building Dependency Tree... Done chupete:/etc/apt# Y a continuacion apt-get install darcs para que efectúe la instalación: chupete:/etc/apt# apt-get install darcs [....] Official i386 Binary-1 (20031201)' in the drive '/cdrom/' and press enter Get:1 http://abridgegame.org woody/ darcs 1.0.3-1 [1051kB] Selecting previously deselected package libgmp3. (Reading database ... 34955 files and directories currently installed.) Unpacking libgmp3 (from .../libgmp3_4.0.1-3_i386.deb) ... Selecting previously deselected package darcs. Unpacking darcs (from .../darcs_1.0.3-1_i386.deb) ... chupete:~# apt-get install darcs-server Es aconsejable instalar también el programa wget, que darcs necesita pero no comprueba su existencia: chupete:~# apt-get install wget La instalación ha sido sencilla y sin dificultades; no en vano su autor afirma que la distribución donde lo ha desarrollado y probado es Debian. === Realizando el primer cambio === Igual que en el caso anterior, para las pruebas hemos utilizado el mismo proyecto "hello world" que en el caso de Aegis. El comando que crea el repositorio es "darcs initialize", que crea un directorio "_darcs" en el propio subdirectorio del proyecto: pruebas2@chupete:~/hello_world> ls AUTHORS README config.log hello_world.kdevses COPYING TODO config.status hello_world.lsm ChangeLog acinclude.m4 configure libtool INSTALL aclocal.m4 configure.files stamp-h.in Makefile admin configure.in stamp-h1 Makefile.am autom4te.cache configure.in.in subdirs Makefile.dist config.h hello_world Makefile.in config.h.in hello_world.kdevprj pruebas2@chupete:~/hello_world> darcs initialize pruebas2@chupete:~/hello_world> ls AUTHORS Makefile.in autom4te.cache configure.in stamp-h.in COPYING README config.h configure.in.in stamp-h1 ChangeLog TODO config.h.in hello_world subdirs INSTALL _darcs config.log hello_world.kdevprj Makefile acinclude.m4 config.status hello_world.kdevses Makefile.am aclocal.m4 configure hello_world.lsm Makefile.dist admin configure.files libtool El siguiente paso consiste en añadir todos los ficheros del proyecto al repositorio del mismo: pruebas2@chupete:~/hello_world> darcs add * -r Y luego se efectúa un checkout de todos los ficheros, que en el lenguaje de darcs implica grabar todos los cambios: pruebas2@chupete:~/hello_world> darcs record --all Darcs needs to know what name (conventionally an email address) to use as the patch author, e.g. 'Fred Bloggs '. If you provide one now it will be stored in the file '_darcs/prefs/author' and used as a default in the future. To change your preferred author address, simply delete or edit this file. What is your email address? Pruebas Darcs What is the patch name? Initial Release Do you want to add a long comment? [yn] Initial Release ***DARCS*** Aquí pondremos la descripción larga del parche, que en este caso es la creación inicial del repositorio. --Fin del Fichero-- Después de realizados unos cambios en el proyecto, el propio darcs nos informa de qué es lo que hemos cambiado en el mismo mediante el comando ''whatsnew'': pruebas2@chupete:~/hello_world> darcs whatsnew { hunk ./Makefile 42 -build_triplet = i686-pc-linux-gnu -host_triplet = i686-pc-linux-gnu -target_triplet = i686-pc-linux-gnu -ACLOCAL = ${SHELL} /home/rluna/hello_world/admin/missing --run aclocal-1.7 +build_triplet = i586-pc-linux-gnu +host_triplet = i586-pc-linux-gnu +target_triplet = i586-pc-linux-gnu +ACLOCAL = ${SHELL} /home/pruebas2/hello_world/admin/missing --run aclocal-1.7 hunk ./Makefile 48 -AMTAR = ${SHELL} /home/rluna/hello_world/admin/missing --run tar -AUTOCONF = ${SHELL} /home/rluna/hello_world/admin/missing --run autoconf +AMTAR = ${SHELL} /home/pruebas2/hello_world/admin/missing --run tar +AUTOCONF = ${SHELL} /home/pruebas2/hello_world/admin/missing --run autoconf hunk ./Makefile 51 [...] } Igual que en el caso anterior, simplemente ejecutando ''darcs record'' guardaremos los datos relativos al cambio en el repositorio de código fuente: pruebas2@chupete:~/hello_world> darcs record hunk ./Makefile 42 -build_triplet = i686-pc-linux-gnu [....] What is the patch name? Tras ejecutar configure Do you want to add a long comment? [yn] n Finished recording patch 'Tras ejecutar configure' Una nueva ejecución del comando ''whatsnew'' indica que no hay cambios, por lo que la información en el repositorio se ha guardado con éxito. === Realizando un cambio concurrente === Ahora vamos a realizar un cambio al repositorio desde dos ubicaciones/ ususarios distintos, y vamos a observar cómo se gestionan las colisiones sobre el mismo fichero de código fuente. == Haciendo que el repositorio sea visible públicamente == El primer paso para realizar esto es hacer que el repositorio que hemos creado, que por ahora sólo es visible para nuestro usuario, lo sea para el resto del mundo. Hemos creado un directorio /var/www/repos para almacenar los repositorios que darcs crea en esta máquina, a continuación hemos hecho un enlace simbólico a nuestro proyecto desde el mismo: chupete:/var/www/repos# ls chupete:/var/www/repos# ln -s /home/pruebas2/hello_world . chupete:/var/www/repos# ls -la total 8 drwxr-xr-x 2 root root 4096 Jul 26 12:07 . drwxr-xr-x 3 root root 4096 Jul 26 2005 .. lrwxrwxrwx 1 root root 26 Jul 26 12:07 hello_world -> /home/pruebas2/hello_world == Descargando el repositorio para trabajar con él == El segundo paso es que otro usuario -sea pruebas1- se descargue una copia completa del código fuente así publicado y pueda trabajar con él. Para ello pruebas1 ha creado un directorio de trabajo con un repositorio vacío: pruebas1@chupete:~> mkdir hello_world pruebas1@chupete:~> cd hello_world pruebas1@chupete:~/hello_world> darcs initialize Y mediante el comando darcs pull ha solicitado una copia completa de todos parches que tiene el usuario pruebas2 en su repositorio: pruebas1@chupete:~/hello_world> darcs pull http://localhost/repos/hello_world Sat Jul 23 11:47:38 CEST 2005 Pruebas Darcs * Initial Release Shall I pull this patch? (1/2) [ynWvpxqadjk], or ? for help: y Sat Jul 23 18:23:02 CEST 2005 Pruebas Darcs * Tras ejecutar configure Shall I pull this patch? (2/2) [ynWvpxqadjk], or ? for help: y pruebas1@chupete:~/hello_world> darcs pull http://localhost/repos/hello_world Sat Jul 23 11:47:38 CEST 2005 Pruebas Darcs * Initial Release Shall I pull this patch? (1/2) [ynWvpxqadjk], or ? for help: pruebas1@chupete:~/hello_world> ls AUTHORS Makefile.in autom4te.cache configure.in libtool COPYING README config.h configure.in.in stamp-h.in ChangeLog TODO config.h.in darcs stamp-h1 INSTALL _darcs config.log hello_world subdirs Makefile acinclude.m4 config.status hello_world.kdevprj Makefile.am aclocal.m4 configure hello_world.kdevses Makefile.dist admin configure.files hello_world.lsm == Efectuando un cambio concurrente == En darcs, los dos usuarios tienen su propio repositorio de código fuente; por lo que el cambio concurrente se puede producir de dos formas: cuando el usuario pruebas1 importa un cambio a su repositorio que entra en conflicto con un cambio que haya realizado él mismo y guardado, o bien cuando el usuario pruebas2 importa un cambio realizado por el usuario pruebas1 que entra en conflicto con los cambios realizados por él. Entendiendo que la raiz del problema es más o menos la misma, optaremos por probar uno de los casos: cuando el usuario pruebas2 ha realizado un cambio y éste entra en conflicto con el cambio que el usuario pruebas1 ha hecho en su propio repositorio. Hacerlo de este modo es más fácil ya que así el usuario pruebas1 no necesita hacer su repositorio accesible públicamente. Comenzaremos por realizar los cambios con el usuario pruebas2 y guardarlos en el repositorio: pruebas2@chupete:~> cd hello_world pruebas2@chupete:~/hello_world> joe hello_world/main.cpp int main(int argc, char *argv[]) { cout << "Hello, World! + change made by pruebas2" << endl; return EXIT_SUCCESS; } pruebas2@chupete:~/hello_world> make pruebas2@chupete:~/hello_world> hello_world/hello_world Hello, World! + change made by pruebas2 Bien, el cambio ya está hecho. Ahora lo que haremos será guardar los cambios en nuestro repositorio local: pruebas2@chupete:~/hello_world> darcs whatsnew { binary ./hello_world/hello_world hunk ./hello_world/main.cpp 27 - cout << "Hello, World!" << endl; + cout << "Hello, World! + change made by pruebas2" << endl; } pruebas2@chupete:~/hello_world> darcs record binary ./hello_world/hello_world Shall I record this patch? (1/2) [ynWsfqadjk], or ? for help: y hunk ./hello_world/main.cpp 27 - cout << "Hello, World!" << endl; + cout << "Hello, World! + change made by pruebas2" << endl; Shall I record this patch? (2/2) [ynWsfqadjk], or ? for help: y What is the patch name? Change on the line hello world Do you want to add a long comment? [yn] n Finished recording patch 'Change on the line hello world' Ahora el usuario pruebas1 hará un cambio en su propio repositorio: pruebas1@chupete:~/hello_world> joe hello_world/main.cpp int main(int argc, char *argv[]) { cout << "Hello, World! + CHANGE MADE BY PRUEBAS1" << endl; return EXIT_SUCCESS; } Y lo guardará en darcs: pruebas1@chupete:~/hello_world> darcs whatsnew { hunk ./hello_world/main.cpp 27 - cout << "Hello, World!" << endl; + cout << "Hello, World! + CHANGE MADE BY PRUEBAS1" << endl; } pruebas1@chupete:~/hello_world> darcs record Darcs needs to know what name (conventionally an email address) to use as the patch author, e.g. 'Fred Bloggs '. If you provide one now it will be stored in the file '_darcs/prefs/author' and used as a default in the future. To change your preferred author address, simply delete or edit this file. What is your email address? Pruebas Darcs Uno hunk ./hello_world/main.cpp 27 - cout << "Hello, World!" << endl; + cout << "Hello, World! + CHANGE MADE BY PRUEBAS1" << endl; Shall I record this patch? (1/1) [ynWsfqadjk], or ? for help: y What is the patch name? change on the hello world line Do you want to add a long comment? [yn] n Finished recording patch 'change on the hello world line' Y ahora integramos los cambios de pruebas2 en el repositorio de pruebas1, lo que provocará una colisión: pruebas1@chupete:~/hello_world> darcs pull http://localhost/repos/hello_world Pulling from "http://localhost/repos/hello_world"... Fri Jul 29 12:14:21 CEST 2005 Pruebas Darcs * Change on the line hello world Shall I pull this patch? (1/1) [ynWvpxqadjk], or ? for help: y We have conflicts in the following files: ./hello_world/main.cpp Finished pulling and applying. El cambio aparece en el código fuente así: int main(int argc, char *argv[]) { v v v v v v v cout << "Hello, World! + CHANGE MADE BY PRUEBAS1" << endl; ************* cout << "Hello, World! + change made by pruebas2" << endl; ^ ^ ^ ^ ^ ^ ^ return EXIT_SUCCESS; } Que obviamente al compilar produce un error y permite la corrección. === Conclusiones === Darcs es un sistema de control de código fuente distribuido. Cada uno de los desarrolladores tiene su propio repositorio de código fuente donde almacena sus propios cambios. Mediante su publicación en un sitio web, estos cambios se pueden hacer visibles para el resto de desarrolladores. A mi modo de ver, un sistema de control de código fuente distribuido, incluso en un modelo de desarrollo como lo es el del software libre, presenta una gran desventaja cuando el número de desarrolladores es grande; ya que la sincronización de los cambios habrá de hacerse forzosamente en un repositorio común o de lo contrario existe un gran riesgo de que alguno de los miembros del equipo acabe con una versión del proyecto diferente. Por otra parte, he encontrado que el aprendizaje de la herramienta es muy rápido y simple: comienza por unos ejemplos básicos para ir adentrandose poco a poco en cuestiones más complicadas. La resolución de los conflictos se realiza directamente en el fichero implicado -no se ha hecho prueba de lo que ocurriría con los ficheros binarios-, que en el caso del código fuente puede ser una solución válida ya que al compilar se explicitaría un error. Sin embargo, los conflictos en ficheros de texto planos -un changelog, por ejemplo- podrían pasar desapercibidos al no requerir la intervención del desarrollador para su integración. Aunque también puede verse el defecto como una ventaja, ya que eso permite la integración automática de parches al repositorio sin la intervención del usuario más que en la supervisión del mismo. Otra cosa que sería interesante destacar, aunque no se ha podido verificar, es que existen versiones para otras plataformas: MacOS X, FreeBSD y Windows. ==== Aegis ==== === Creación del proyecto === Los pasos que se van a dar a continuación se efectuarán con el perfil de administrador de aegis, que es el usuario "pruebas1". No hemos tenido que hacer nada especial para informar a aegis que éste es el usuario administrador; simplemente hemos realizado todas las acciones administrativas con este usuario. Para guardar todos los proyectos hemos creado un directorio /aegis, que inicialmente estará vacío. Como proyecto de pruebas hemos creado mediante kdeveloper un programa "Hello World!". kdeveloper ha creado los ficheros necesarios para autoconf y automake. Mediante el comando aenpr crearemos el proyecto y a continuación establecemos las características del mismo mediante aepa: pruebas1@chupete:~> aenpr hello_world -dir /aegis/hello_world -version - aegis: project "hello_world": new project complete pruebas1@chupete:~> aepa -p hello_world /* ** Project hello_world */ description = "The \"hello_world\" program."; developer_may_review = false; developer_may_integrate = false; reviewer_may_integrate = false; developers_may_create_changes = false; umask = 022; default_test_exemption = false; minimum_change_number = 10; reuse_change_numbers = true; minimum_branch_number = 1; skip_unlucky = false; compress_database = false; develop_end_action = goto_being_reviewed; === Asignacion de las figuras de desarrollador, revisor e integrador === Áhora añadimos al proyecto las figuras de desarrollador -pruebas2-, revisor -pruebas3- e integrador -pruebas4-: pruebas1@chupete:~> aend pruebas2 -p hello_world aegis: project "hello_world": new developer pruebas2 complete pruebas1@chupete:~> aenrv pruebas3 -p hello_world aegis: project "hello_world": new reviewer pruebas3 complete pruebas1@chupete:~> aeni pruebas4 -p hello_world aegis: project "hello_world": new integrator pruebas4 complete === Creamos la primera rama del proyecto === Siguiendo con el usuario administrador de aegis -pruebas1-, hemos creado la rama 1.0 para comenzar los cambios al proyecto: pruebas1@chupete:~> ael p List of Projects Page 1 Sat Jul 16 23:34:10 2005 Project Directory Description --------- ----------- ------------- hello_world /aegis/hello_world The "hello_world" program. pruebas1@chupete:~> aenbr -p hello_world 1 aegis: project "hello_world": change 1: development directory "/aegis/hello_world/branch.1" aegis: project "hello_world.1": new branch complete pruebas1@chupete:~> aenbr -p hello_world.1 0 aegis: project "hello_world.1": change 0: development directory "/aegis/hello_world/branch.1/branch.0" aegis: project "hello_world.1.0": new branch complete === Damos de alta el primer cambio del proyecto === pruebas1@chupete:~> aenc -edit -p hello_world.1.0 aegis: project "hello_world.1.0": change 10: new change complete /* ** nc dflt hint */ brief_description = "Comenzamos con el proyecto"; description = "Primera instalacion de ficheros"; cause = internal_bug; test_exempt = true; test_baseline_exempt = true; regression_test_exempt = true; architecture = [ "unspecified", ]; === El desarrollador realiza el primer cambio === Ahora entramos como el usuario pruebas2 al objeto de hacer la primera subida del proyecto. La primera subida de ficheros es la más compleja, ya que hay que crear el fichero aegis.conf con la información necesaria para que el almacenamiento de versiones de los fuentes y la construcción del fichero se resuelvan satisfactoriamente. Comenzaremos por visualizar el cambio que tenemos pendiente: pruebas2@chupete:~> aedb -l -p hello_world.1.0 Project "hello_world.1.0" Page 1 List of Changes awaiting_development Sat Jul 16 23:56:38 2005 Change State Description ------- ------- ------------- 10 awaiting_ Comenzamos con el proyecto development A continuación informamos a Aegis que comenzamos la fase de desarrollo. Aegis hace una copia de la rama activa tal y como está en el repositorio en un directorio creado a tal efecto para nosotros: pruebas2@chupete:~> aedb hello_world.1.0 10 aegis: project "hello_world.1.0": change 10: development directory "/home/pruebas2/hello_world.1.0.C010" aegis: logging to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: develop begin complete Y a continuación informamos a Aegis que pasamos a trabajar en el directorio del proyecto mediante el comando aecd: pruebas2@chupete:~> aecd -p hello_world.1.0 -c 10 aegis: project "hello_world.1.0": change 10: change directory /home/pruebas2/hello_world.1.0.C010 complete Nuestro paso siguiente será hacer una copia completa del proyecto "hello_world" tal y como lo hemos creado con kdeveloper. pruebas2@chupete:~> ls hello_world hello_world.1.0.C010 pruebas2@chupete:~> cd hello_world.1.0.C010/ pruebas2@chupete:~/hello_world.1.0.C010> ls aegis.log pruebas2@chupete:~/hello_world.1.0.C010> cp ../hello_world/* . -R Añadiremos al proyecto todos los ficheros como nuevos: pruebas2@chupete:~/hello_world.1.0.C010> ls AUTHORS Makefile.in aegis.log configure.files libtool COPYING README autom4te.cache configure.in stamp-h.in ChangeLog TODO config.h configure.in.in stamp-h1 INSTALL acinclude.m4 config.h.in hello_world subdirs Makefile aclocal.m4 config.log hello_world.kdevprj Makefile.am admin config.status hello_world.kdevses Makefile.dist aegis.conf configure hello_world.lsm pruebas2@chupete:~/hello_world.1.0.C010> foreach i ( * ) foreach? aenf $i -p hello_world.1.0 -c 10 foreach? end aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file AUTHORS completed aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file COPYING completed aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file ChangeLog completed aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log .... aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file hello_world.kdevses completed aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file hello_world.lsm completed aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file libtool completed aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file stamp-h.in completed aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file stamp-h1 completed aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file subdirs completed pruebas2@chupete:~/hello_world.1.0.C010> El manual de usuario dice que se tiene que aportar un fichero aegis.conf en la primera versión del proyecto, donde se registra cómo se construye el proyecto, qué herramientas de control de versiones se están utilizando, etc. Sin embargo, los pasos para crearlo no están muy claros; nosotros hemos optado por copiar de /usr/doc/aegis/examples/config.example las partes que pueden sernos de utilidad: pruebas2@chupete:/usr/doc/aegis/examples/config.example> cat make rcs diff >~/aegis.conf En principio, daremos los valores por defecto como válidos. El uso de make con aegis requiere de algunos "trucos" según su autor -que recomienda que en su lugar usemos la herramienta cook que se integra mejor con aegis-. No obstante, para los proyectos autoconf y automake hace una salvedad. En el manual de usuario, en la página 79 pueden leerse las instrucciones concretas para llevar a cabo esta fase para el caso de proyectos que usen Autoconf, como el nuestro. El primer paso es reemplazar build_command por "exit 0" en el fichero de configuración "aegis.conf" para que añada todos los ficheros al proyecto sin construir: /* * The build_command field of the project config file is used to invoke the * relevant build command. This command tells make where to find the rules. * The ${s Makefile} expands to a path into the baseline during development * if the file is not in the change. Look in aesub(5) for more information * about command substitutions. */ build_command = "exit 0"; /* "gmake -f ${s Makefile} project=$p change=$c version=$v"; */ Y el siguiente paso consistirá en informar a Aegis de este nuevo fichero. Tras varias pruebas infructuosas, nos dimos cuenta que el nombre que aegis espera para este fichero no es "aegis.conf" sino "config", así que procedemos a renombrarlo e informar a Aegis del nuevo fichero que hay en el proyecto: pruebas2@chupete:~/hello_world.1.0.C010> aenf -p hello_world.1.0 -c 10 config aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: new file aegis.conf completed El mensaje que salía puede verse aquí: pruebas2@chupete:~/hello_world.1.0.C010> aeb -p hello_world.1.0 -c 10 aegis: project "hello_world.1.0": change 10: you must create a "config" file pruebas2@chupete:~/hello_world.1.0.C010> Una vez introducidos todos los cambios, procedemos a construir el proyecto. Como es la primera vez, hemos puesto "exit 0" en el comando de construcción para que nos permita realizarlo sin necesidad de hacer una construcción real del mismo: pruebas2@chupete:~/hello_world.1.0.C010> aeb -p hello_world.1.0 -c 10 aegis: appending log to /home/pruebas2/hello_world.1.0.C010/aegis.log aegis: project "hello_world.1.0": change 10: creating symbolic links to baseline aegis: project "hello_world.1.0": change 10: development build started aegis: exit 0 aegis: project "hello_world.1.0": change 10: development build complete El siguiente paso es indicar a Aegis que el desarrollo a finalizado. Primero generamos los ficheros de diferencias, que se usan para las revisiones. pruebas2@chupete:~/hello_world.1.0.C010> aed [....] aegis: set +e; diff -U10 -a /dev/null /home/pruebas2/hello_world.1.0.C010/ hello_world/main.cpp > /home/pruebas2/hello_world.1.0.C010/hello_world/ main.cpp,D; test $? -le 1 aegis: set +e; diff -U10 -a /dev/null /home/pruebas2/hello_world.1.0.C010/ hello_world/main.o > /home/pruebas2/hello_world.1.0.C010/hello_world/ main.o,D; test $? -le 1 aegis: set +e; diff -U10 -a /dev/null /home/pruebas2/hello_world.1.0.C010/ hello_world/templates/cpp_template > /home/pruebas2/hello_world.1.0.C010/hello_world/templates/cpp_template, D; test $? -le 1 aegis: set +e; diff -U10 -a /dev/null /home/pruebas2/hello_world.1.0.C010/ hello_world/templates/header_template > /home/pruebas2/hello_world.1.0.C010/hello_world/templates/header_ template,D; test $? -le 1 [....] Tras esta operación se crearán numerosos ficheros con extensión *,D -uno por cada fichero que se ha cambiado o creado-. Estos ficheros de diferencias permiten ver a los revisores e integradores qué se ha hecho en cada cambio. pruebas2@chupete:~/hello_world.1.0.C010> ls AUTHORS README config.h,D hello_world.kdevprj AUTHORS,D README,D config.h.in hello_world.kdevprj,D COPYING TODO config.h.in,D hello_world.kdevses COPYING,D TODO,D config.log hello_world.kdevses,D ChangeLog acinclude.m4 config.log,D hello_world.lsm ChangeLog,D acinclude.m4,D config.status hello_world.lsm,D INSTALL aclocal.m4 config.status,D libtool INSTALL,D aclocal.m4,D configure libtool,D Makefile admin configure,D stamp-h.in Makefile,D aegis.conf configure.files stamp-h.in,D Makefile.am aegis.conf,D configure.files,D stamp-h1 Makefile.am,D aegis.log configure.in stamp-h1,D Makefile.dist autom4te.cache configure.in,D subdirs Makefile.dist,D config configure.in.in subdirs,D Makefile.in config,D configure.in.in,D Makefile.in,D config.h hello_world Y finalmente damos por finalizado el cambio: pruebas2@chupete:~/hello_world.1.0.C010> aede aegis: project "hello_world.1.0": change 10: development completed Con lo que queda finalizado el primer cambio. === La revisión e integración del primer cambio === A diferencia de otros sistemas de control de código fuente, Aegis exige que los cambios que se realizan estén revisados y e integrados. == La revisión del cambio == En primer lugar entraremos como el revisor y daremos por válido el cambio realizado en el proyecto. Comenzaremos por visualizar los cambios que tenemos pendientes para revisar: pruebas3@chupete:~> aerpass -l -p hello_world.1.0 Project "hello_world.1.0" Page 1 List of Changes being_reviewed Fri Jul 29 17:44:00 2005 Change State Description ------- ------- ------------- 10 being_reviewed Comenzamos con el proyecto El siguiente paso será cambiar al directorio donde se ha realizado el cambio, que está dentro del directorio "home" del usuario "pruebas2": pruebas3@chupete:~> aecd -p hello_world.1.0 10 aegis: project "hello_world.1.0": change 10: change directory /home/pruebas2/hello_world.1.0.C010 complete pruebas3@chupete:/home/pruebas2/hello_world.1.0.C010> Para que el grupo de desarrollo pueda trabajar dentro del directorio del cambio, aegis establece los permisos adecuados del subdirectorio que contiene el cambio, hello_world.1.0.C010. En concreto, establece el 'sticky bit' del grupo al que pertenece el equipo de desarrollo, que en este caso es "users". El comando ''aedmore'' nos permite revisar todos los cambios que se han realizado de una forma rápida: pruebas3@chupete:/home/pruebas2/hello_world.1.0.C010> aedmore :::::::::::::: ./AUTHORS,D :::::::::::::: --- /dev/null Fri Nov 19 23:44:21 2004 +++ /home/pruebas2/hello_world.1.0.C010/AUTHORS Thu Jul 21 13:02:18 2005 @@ -0,0 +1 @@ +Raul Luna Rodriguez --More--(Next file: ./COPYING,D) Y finalmente, si estimamos que el cambio se ha realizado correctamente, damos por válido el mismo: pruebas3@chupete:/home/pruebas2/hello_world.1.0.C010> aerpass -p hello_world.1.0 10 aegis: project "hello_world.1.0": change 10: review pass complete Si consideraramos que el cambio no cumple algún requisito y quisieramos rechazarlo, utilizaríamos el comando "aerfail". == La integración del cambio == Para ello entraremos como integrador -usuario "pruebas4"- y comenzaremos por revisar qué cambios están pendientes de integración: pruebas4@chupete:~> aeib -l -p hello_world.1.0 Project "hello_world.1.0" Page 1 List of Changes awaiting_integration Fri Jul 29 18:26:09 2005 Change State Description ------- ------- ------------- 10 awaiting_ Comenzamos con el proyecto integration Pasamos al directorio donde están los ficheros del cambio: pruebas4@chupete:~> aecd -p hello_world.1.0 10 aegis: project "hello_world.1.0": change 10: change directory /home/pruebas2/hello_world.1.0.C010 complete Comenzamos por informar a aegis que la integración del cambio ha comenzado: pruebas4@chupete:/home/pruebas2/hello_world.1.0.C010> aeib -p hello_world.1.0 10 aegis: project "hello_world.1.0": change 10: link baseline to integration directory aegis: project "hello_world.1.0": change 10: apply change to integration directory aegis: logging to /aegis/hello_world/branch.1/branch.0/delta685.001/aegis.log aegis: project "hello_world.1.0": change 10: integrate begin complete El integrador debe construir y probar cada cambio: pruebas4@chupete:/home/pruebas2/hello_world.1.0.C010> aeb aegis: logging to /aegis/hello_world/branch.1/branch.0/delta685.001/aegis.log aegis: project "hello_world.1.0": change 10: creating symbolic links to baseline aegis: project "hello_world.1.0": change 10: integration build started aegis: cd /aegis/hello_world/branch.1/branch.0/delta685.001 aegis: user "pruebas1", group "users" aegis: exit 0 aegis: project "hello_world.1.0": change 10: integration build complete aegis: project "hello_world.1.0": change 10: removing symbolic links to baseline pruebas4@chupete:/home/pruebas2/hello_world.1.0.C010> aet pruebas4@chupete:/home/pruebas2/hello_world.1.0.C010> Y finalmente ya podemos dar la fase de integración como "pasada" y con ello el cambio formará parte de la nueva línea base del proyecto: pruebas4@chupete:/home/pruebas2> aeipass -p hello_world.1.0 10 [un montón de información sobre la integración] Como efecto "colateral" el directorio del cambio se ha borrado, y este cambio figura ya como completado: pruebas1@chupete:~> ael c -p hello_world.1.0 Project "hello_world.1.0" Page 1 List of Changes Fri Jul 29 18:37:51 2005 Change State Description ------- ------- ------------- 10 completed Comenzamos con el proyecto === Efectuando un cambio concurrente === Vamos a ver cómo gestiona Aegis aquellos cambios que afecten a la misma línea de código. Para ello designaremos a pruebas2 y pruebas3 como desarrolladores y les asignaremos un cambio, de tal forma que las modificaciones que harán al proyecto afectarán a la misma línea de código. == El administrador añade un nuevo desarrollador al proyecto == pruebas1@chupete:~> aend pruebas3 -p hello_world.1.0 aegis: project "hello_world.1.0": new developer pruebas3 complete == El administrador crea dos cambios nuevos == Se crearán dos cambios nuevos, uno para el desarrollador pruebas2 y otro para el desarrollador pruebas3: pruebas1@chupete:~> aenc -edit -p hello_world.1.0 /* ** Project hello_world.1.0 ** nc dflt hint */ brief_description = "Change the 'hello world' line of main.cpp."; description = "Change for developer pruebas2"; cause = internal_bug; test_exempt = false; test_baseline_exempt = false; regression_test_exempt = true; architecture = [ "unspecified", ]; ~ ~ ~ /home/pruebas1/aegis-1766-1: 14 lines, 298 characters. aegis: project "hello_world.1.0": change 11: new change complete Y el cambio para el usuario pruebas3: pruebas1@chupete:~> aenc -edit -p hello_world.1.0 /* ** Project hello_world.1.0 ** nc dflt hint */ brief_description = "Change the 'Hello World' line of the main program."; description = "Change for developer pruebas3"; cause = internal_bug; test_exempt = false; test_baseline_exempt = false; regression_test_exempt = true; architecture = [ "unspecified", ]; ~ /home/pruebas1/aegis-1777-1: 14 lines, 306 characters. aegis: project "hello_world.1.0": change 12: new change complete == El primer desarrollador lleva a cabo el cambio == Igual que en otros casos, comenzamos por visualizar los cambios pendientes: pruebas2@chupete:~> aedb -l -p hello_world.1.0 Project "hello_world.1.0" Page 1 List of Changes awaiting_development Fri Jul 29 13:43:28 2005 Change State Description ------- ------- ------------- 11 awaiting_ Change the 'hello world' line of main.cpp. development 12 awaiting_ Change the 'Hello World' line of the main program. development Y a continuación nos apropiamos del cambio 11: pruebas2@chupete:~> aedb -p hello_world.1.0 11 aegis: project "hello_world.1.0": change 11: development directory "/home/pruebas2/hello_world.1.0.C011" aegis: logging to /home/pruebas2/hello_world.1.0.C011/aegis.log aegis: project "hello_world.1.0": change 11: creating symbolic links to baseline aegis: project "hello_world.1.0": change 11: develop begin complete Y comenzamos el proceso de desarrollo haciendo un "cd" al directorio con los cambios que se nos ha creado: pruebas2@chupete:~> aecd -p hello_world.1.0 11 aegis: project "hello_world.1.0": change 11: change directory /home/pruebas2/hello_world.1.0.C011 complete pruebas2@chupete:~/hello_world.1.0.C011> El directorio de cambios ahora creado es muy distinto del original: todos los ficheros son enlaces simbólicos a los correspondientes ficheros que quedaron almacenados en el cambio anterior: pruebas2@chupete:~/hello_world.1.0.C011> ls -la | more total 24 drwxr-sr-x 5 pruebas2 users 4096 Jul 29 18:46 . drwxr-xr-x 4 pruebas2 users 4096 Jul 29 18:46 .. lrwxrwxrwx 1 pruebas2 users 53 Jul 29 18:46 AUTHORS -> /aegis/hello_world/branch.1/branch.0/baseline/AUTHORS lrwxrwxrwx 1 pruebas2 users 55 Jul 29 18:46 AUTHORS,D -> /aegis/hello_world/branch.1/branch.0/baseline/AUTHORS,D lrwxrwxrwx 1 pruebas2 users 53 Jul 29 18:46 COPYING -> /aegis/hello_world/branch.1/branch.0/baseline/COPYING lrwxrwxrwx 1 pruebas2 users 55 Jul 29 18:46 COPYING,D -> /aegis/hello_world/branch.1/branch.0/baseline/COPYING,D [....] Aquellos ficheros que vayan a ser modificados han de ser "copiados" en local mediante el comando aecp -obviamente, existen los comandos aerm y aemv, además de sus correspondientes comandos "u" aecpu, aermu, aemvu para deshacerlos-. pruebas2@chupete:~/hello_world.1.0.C011> aecp hello_world/main.cpp aegis: appending log to /home/pruebas2/hello_world.1.0.C011/aegis.log aegis: project "hello_world.1.0": change 11: copied hello_world/main.cpp aegis: project "hello_world.1.0": change 11: copy file complete Y ahora sí procedemos a realizar el cambio: int main(int argc, char *argv[]) { cout << "Hello, World! + Cambio realizado por el usuario pruebas2 " << endl; return EXIT_SUCCESS; } Seguimos manteniendo las fases de construcción y realización de test como triviales para mantener las pruebas dentro de unos cauces de simplicidad razonables. De este modo, indicamos a Aegis que construya el proyecto: pruebas2@chupete:~/hello_world.1.0.C011> aeb aegis: logging to /home/pruebas2/hello_world.1.0.C011/aegis.log aegis: project "hello_world.1.0": change 11: creating symbolic links to baseline aegis: project "hello_world.1.0": change 11: development build started aegis: exit 0 aegis: project "hello_world.1.0": change 11: development build complete Que cree los ficheros de diferencias: pruebas2@chupete:~/hello_world.1.0.C011> aed aegis: appending log to /home/pruebas2/hello_world.1.0.C011/aegis.log aegis: set +e; diff -U10 -a /aegis/hello_world/branch.1/branch.0/baseline/ hello_world/main.cpp /home/pruebas2/hello_world.1.0.C011/hello_world/ main.cpp > /home/pruebas2/hello_world.1.0.C011/hello_world/main.cpp,D; test $? -le 1 aegis: project "hello_world.1.0": change 11: difference complete Creamos un test para el cambio que acabamos de hacer. Ya que no vamos a construir el programa, simplemente verificaremos que la línea en cuestión se encuentra en el fichero main.cpp: pruebas2@chupete:~/hello_world.1.0.C011> aent aegis: appending log to /home/pruebas2/hello_world.1.0.C011/aegis.log aegis: project "hello_world.1.0": change 11: new test test/00/t0001a.sh complete pruebas2@chupete:~/hello_world.1.0.C011> joe test/00/t0001a.sh #!/bin/sh tmp=/tmp/$$ here=$(pwd) fail() { echo FAILED 1>&2 cd $here rm -rf $tmp exit 1 } pass() { cd $here rm -rf $tmp exit 0 } trap "fail" 1 2 3 15 # comprobamos que el fichero "main.cpp" contiene # efectivamente el cambio introducido ya que # de lo contrario el test de regresion no ' # falla grep 'Cambio realizado por el usuario pruebas2' hello_world/main.cpp if [ $? -ne 0 ] then fail fi # si llega hasta aquí, es que ha funcionado pass Probamos el test: pruebas2@chupete:~/hello_world.1.0.C011> aet aegis: appending log to /home/pruebas2/hello_world.1.0.C011/aegis.log aegis: /bin/sh /home/pruebas2/hello_world.1.0.C011/test/00/t0001a.sh cout << "Hello, World! + Cambio realizado por el usuario pruebas2 " << endl; aegis: project "hello_world.1.0": change 11: test/00/t0001a.sh pass aegis: project "hello_world.1.0": change 11: passed 1 tests pruebas2@chupete:~/hello_world.1.0.C011> aet -bl aegis: appending log to /home/pruebas2/hello_world.1.0.C011/aegis.log aegis: cd /aegis/hello_world/branch.1/branch.0/baseline aegis: /bin/sh /home/pruebas2/hello_world.1.0.C011/test/00/t0001a.sh FAILED aegis: project "hello_world.1.0": change 11: test/00/t0001a.sh baseline fail, good aegis: project "hello_world.1.0": change 11: passed 1 tests E indicamos la finalización del cambio: pruebas2@chupete:~/hello_world.1.0.C011> aed aegis: logging to /home/pruebas2/hello_world.1.0.C011/aegis.log aegis: set +e; diff -U10 -a /dev/null /home/pruebas2/hello_world.1.0.C011/test/ 00/t0001a.sh > /home/pruebas2/hello_world.1.0.C011/test/00/t0001a.sh,D; test $? -le 1 aegis: project "hello_world.1.0": change 11: difference complete pruebas2@chupete:~/hello_world.1.0.C011> aede aegis: project "hello_world.1.0": change 11: development completed == El segundo desarrollador lleva a cabo el cambio == El proceso es básicamente el mismo que en el caso anterior: Nos "apropiamos" del cambio 12: pruebas3@chupete:~> aedb -p hello_world.1.0 12 aegis: project "hello_world.1.0": change 12: development directory "/home/pruebas3/hello_world.1.0.C012" aegis: logging to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: project "hello_world.1.0": change 12: creating symbolic links to baseline aegis: project "hello_world.1.0": change 12: develop begin complete Hacemos "cd" al directorio del proyecto: pruebas3@chupete:~> aecd -p hello_world.1.0 12 aegis: project "hello_world.1.0": change 12: change directory /home/pruebas3/hello_world.1.0.C012 complete Copiamos el directorio "main.cpp" al directorio del proyecto: pruebas3@chupete:~/hello_world.1.0.C012> aecp hello_world/main.cpp aegis: appending log to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: project "hello_world.1.0": change 12: copied hello_world/main.cpp aegis: project "hello_world.1.0": change 12: copy file complete Y procedemos a realizar el cambio: pruebas3@chupete:~/hello_world.1.0.C012> joe hello_world/main.cpp int main(int argc, char *argv[]) { cout << "Hello, World! + cambio introducido por pruebas3" << endl; return EXIT_SUCCESS; } Como Aegis lo exige, creamos un test para este cambio: pruebas3@chupete:~/hello_world.1.0.C012> aent -p hello_world.1.0 12 aegis: appending log to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: project "hello_world.1.0": change 12: new test test/00/t0002a.sh complete Y lo completamos con el siguiente script: #!/bin/sh tmp=/tmp/$$ here=$(pwd) fail() { echo FAILED 1>&2 cd $here rm -rf $tmp exit 1 } pass() { cd $here rm -rf $tmp exit 0 } trap "fail" 1 2 3 15 # comprobamos que el fichero "main.cpp" contiene # efectivamente el cambio introducido ya que # de lo contrario el test de regresion no ' # falla grep 'cambio introducido por pruebas3' hello_world/main.cpp if [ $? -ne 0 ] then fail fi pass Construimos el proyecto (esta fase se sigue manteniendo trivial y en realidad sólo realiza un "exit 0"): pruebas3@chupete:~/hello_world.1.0.C012> aeb aegis: logging to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: project "hello_world.1.0": change 12: creating symbolic links to baseline aegis: project "hello_world.1.0": change 12: development build started aegis: exit 0 aegis: project "hello_world.1.0": change 12: development build complete Y ejecutamos los test: pruebas3@chupete:~/hello_world.1.0.C012> aet aegis: logging to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: /bin/sh /home/pruebas3/hello_world.1.0.C012/test/00/t0002a.sh cout << "Hello, World! + cambio introducido por pruebas3" << endl; aegis: project "hello_world.1.0": change 12: test/00/t0002a.sh pass aegis: project "hello_world.1.0": change 12: passed 1 tests pruebas3@chupete:~/hello_world.1.0.C012> aet -bl aegis: appending log to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: cd /aegis/hello_world/branch.1/branch.0/baseline aegis: /bin/sh /home/pruebas3/hello_world.1.0.C012/test/00/t0002a.sh FAILED aegis: project "hello_world.1.0": change 12: test/00/t0002a.sh baseline fail, good aegis: project "hello_world.1.0": change 12: passed 1 tests Creamos los ficheros de diferencias: pruebas3@chupete:~/hello_world.1.0.C012> aed aegis: logging to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: set +e; diff -U10 -a /aegis/hello_world/branch.1/branch.0/baseline/ hello_world/main.cpp /home/pruebas3/hello_world.1.0.C012/hello_world/ main.cpp > /home/pruebas3/hello_world.1.0.C012/hello_world/main.cpp,D; test $? -le 1 aegis: set +e; diff -U10 -a /dev/null /home/pruebas3/hello_world.1.0.C012/test/ 00/t0002a.sh > /home/pruebas3/hello_world.1.0.C012/test/00/t0002a.sh,D; test $? -le 1 aegis: project "hello_world.1.0": change 12: difference complete Y finalizamos el desarrollo: pruebas3@chupete:~/hello_world.1.0.C012> aede aegis: project "hello_world.1.0": change 12: file "hello_world/main.cpp" locked for change 11 aegis: project "hello_world.1.0": change 12: develop end fail Pero no nos deja finalizar el desarrollo porque el fichero "main.cpp" estaba bloqueado por el cambio del usuario anterior: habrá de ser revisado y aprobado el cambio anterior para poder continuar con nuestro cambio. == Se revisa e integra el cambio del primer desarrollador == Debido a que el cambio que hemos hecho en el fichero main.cpp en el primer cambio ha bloqueado este fichero y nos impide continuar con el cambio del segundo desarrollador, procederemos a revisar e integrar el primer cambio antes de continuar. NOTA: El usuario "pruebas3" que se ha usado en estos ejemplos es a la vez desarrollador y revisor: en este apartado actúa como revisor. **Revisión del cambio ** Comenzamos por ver los cambios pendientes de revisión que tenemos: pruebas3@chupete:~/hello_world.1.0.C012> aerpass -l -p hello_world.1.0 Project "hello_world.1.0" Page 1 List of Changes being_reviewed Sat Jul 30 13:06:13 2005 Change State Description ------- ------- ------------- 11 being_reviewed Change the 'hello world' line of main.cpp. Y pasamos al directorio del cambio 11: pruebas3@chupete:~/hello_world.1.0.C012> aecd -p hello_world.1.0 11 aegis: project "hello_world.1.0": change 11: change directory /home/pruebas2/hello_world.1.0.C011 complete Y le damos como válido: pruebas3@chupete:/home/pruebas2/hello_world.1.0.C011> aerpass -p hello_world.1.0 11 aegis: project "hello_world.1.0": change 11: review pass complete **Integración del cambio** Comenzamos la integración del cambio: pruebas4@chupete:~> aeib -p hello_world.1.0 11 aegis: project "hello_world.1.0": change 11: link baseline to integration directory aegis: project "hello_world.1.0": change 11: apply change to integration directory aegis: logging to /aegis/hello_world/branch.1/branch.0/delta568.002/aegis.log aegis: project "hello_world.1.0": change 11: integrate begin complete pruebas4@chupete:~> aecd -p hello_world.1.0 11 aegis: project "hello_world.1.0": change 11: change directory /aegis/hello_world/branch.1/branch.0/delta568.002 complete Construimos: pruebas4@chupete:/aegis/hello_world/branch.1/branch.0/delta568.002> aeb aegis: logging to /aegis/hello_world/branch.1/branch.0/delta568.002/aegis.log aegis: project "hello_world.1.0": change 11: creating symbolic links to baseline aegis: project "hello_world.1.0": change 11: integration build started aegis: user "pruebas1", group "users" aegis: exit 0 aegis: project "hello_world.1.0": change 11: integration build complete aegis: project "hello_world.1.0": change 11: removing symbolic links to baseline Probamos: pruebas4@chupete:/aegis/hello_world/branch.1/branch.0/delta568.002> aet aegis: appending log to /aegis/hello_world/branch.1/branch.0/delta568.002/ aegis.log aegis: /bin/sh /aegis/hello_world/branch.1/branch.0/delta568.002/test/00/ t0001a.sh cout << "Hello, World! + Cambio realizado por el usuario pruebas2 " << endl; aegis: project "hello_world.1.0": change 11: test/00/t0001a.sh pass aegis: project "hello_world.1.0": change 11: passed 1 tests pruebas4@chupete:/aegis/hello_world/branch.1/branch.0/delta568.002> aet -bl aegis: appending log to /aegis/hello_world/branch.1/branch.0/delta568.002/ aegis.log aegis: cd /aegis/hello_world/branch.1/branch.0/baseline aegis: /bin/sh /aegis/hello_world/branch.1/branch.0/delta568.002/test/00/ t0001a.sh FAILED aegis: project "hello_world.1.0": change 11: test/00/t0001a.sh baseline fail, good aegis: project "hello_world.1.0": change 11: passed 1 tests Generamos los ficheros de diferencias: pruebas4@chupete:/aegis/hello_world/branch.1/branch.0/delta568.002> aed aegis: appending log to /aegis/hello_world/branch.1/branch.0/delta568.002/ aegis.log aegis: project "hello_world.1.0": change 11: difference complete Y damos la integración por finalizada, constituyendo una nueva "baseline" del cambio: pruebas4@chupete:/aegis/hello_world/branch.1> aeipass aegis: appending log to /aegis/hello_world/branch.1/branch.0/delta568.002/ aegis.log aegis: cd /aegis/hello_world/history aegis: user "pruebas1", group "users" aegis: ci -u -d -M -m'(1.0.D002) Change the '"'hello world' line of main.cpp." -wpruebas2 -t/dev/null /tmp/aegis-617-2/main.cpp /aegis/hello_world/history/hello_world/main.cpp,v; rcs -U /aegis/hello_world/history/hello_world/main.cpp,v /aegis/hello_world/history/hello_world/main.cpp,v <-- /tmp/aegis-617-2/main.cpp new revision: 1.2; previous revision: 1.1 done RCS file: /aegis/hello_world/history/hello_world/main.cpp,v done aegis: ( rlog -r /aegis/hello_world/history/hello_world/main.cpp,v | awk '/^head:/ {print $2}' ) > /tmp/aegis-617-3 aegis: ci -u -d -M -m'(1.0.D002) Change the '"'hello world' line of main.cpp." -wpruebas2 -t/dev/null /tmp/aegis-617-2/main.cpp /aegis/hello_world/history/hello_world/main.cpp,v; rcs -U /aegis/hello_world/history/hello_world/main.cpp,v /aegis/hello_world/history/hello_world/main.cpp,v <-- /tmp/aegis-617-2/main.cpp new revision: 1.2; previous revision: 1.1 done RCS file: /aegis/hello_world/history/hello_world/main.cpp,v done aegis: ( rlog -r /aegis/hello_world/history/hello_world/main.cpp,v | awk '/^head:/ {print $2}' ) > /tmp/aegis-617-3 aegis: ci -u -d -M -m'(1.0.D002) Change the '"'hello world' line of main.cpp." -wpruebas2 -t/dev/null /tmp/aegis-617-2/t0001a.sh /aegis/hello_world/history/test/00/t0001a.sh,v; rcs -U /aegis/hello_world/history/test/00/t0001a.sh,v /aegis/hello_world/history/test/00/t0001a.sh,v <-- /tmp/aegis-617-2/t0001a.sh initial revision: 1.1 done RCS file: /aegis/hello_world/history/test/00/t0001a.sh,v done aegis: ( rlog -r /aegis/hello_world/history/test/00/t0001a.sh,v | awk '/^head:/ {print $2}' ) > /tmp/aegis-617-4 aegis: project "hello_world.1.0": change 11: adjust file modification times aegis: project "hello_world.1.0": change 11: discard old directories aegis: warning: file times in future aegis: project "hello_world.1.0": change 11: integrate pass complete Ahora es el momento de que el segundo desarrollador pueda completar el cambio que estaba haciendo y que no pudo continuar. == El segundo desarrollador completa el cambio == Al intentar finalizar el cambio, se nos comunica que la baseline del proyecto ha cambiado: pruebas3@chupete:/home/pruebas2/hello_world.1.0.C011> aede aegis: project "hello_world.1.0": change 12: baseline hello_world/main.cpp changed aegis: project "hello_world.1.0": change 12: develop end fail Por lo que es necesario re-copiar main.cpp: pruebas3@chupete:~/hello_world.1.0.C012> aecpu hello_world/main.cpp aegis: appending log to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: project "hello_world.1.0": change 12: hello_world/main.cpp gone pruebas3@chupete:~/hello_world.1.0.C012> aecp hello_world/main.cpp aegis: appending log to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: project "hello_world.1.0": change 12: copied hello_world/main.cpp aegis: project "hello_world.1.0": change 12: copy file complete Y ahora, al realizar el cambio, ya vemos el cambio que el otro desarrollador hizo: int main(int argc, char *argv[]) { cout << "Hello, World! + Cambio realizado por el usuario pruebas2 " << endl; return EXIT_SUCCESS; } Insertaremos nuestro propio cambio: int main(int argc, char *argv[]) { cout << "Hello, World! + Cambio realizado por el usuario pruebas2 " << endl; cout << "cambio introducido por pruebas3" << endl; return EXIT_SUCCESS; } Y ahora sí que podremos finalizar nuestro desarrollo. Construir, probar y diferencias: pruebas3@chupete:~/hello_world.1.0.C012> aeb aegis: logging to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: project "hello_world.1.0": change 12: creating symbolic links to baseline aegis: project "hello_world.1.0": change 12: development build started aegis: exit 0 aegis: project "hello_world.1.0": change 12: development build complete pruebas3@chupete:~/hello_world.1.0.C012> aet aegis: appending log to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: /bin/sh /home/pruebas3/hello_world.1.0.C012/test/00/t0002a.sh cout << "cambio introducido por pruebas3" << endl; aegis: project "hello_world.1.0": change 12: test/00/t0002a.sh pass aegis: project "hello_world.1.0": change 12: passed 1 tests pruebas3@chupete:~/hello_world.1.0.C012> aet -bl aegis: appending log to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: cd /aegis/hello_world/branch.1/branch.0/baseline aegis: /bin/sh /home/pruebas3/hello_world.1.0.C012/test/00/t0002a.sh FAILED aegis: project "hello_world.1.0": change 12: test/00/t0002a.sh baseline fail, good aegis: project "hello_world.1.0": change 12: passed 1 tests pruebas3@chupete:~/hello_world.1.0.C012> aed aegis: appending log to /home/pruebas3/hello_world.1.0.C012/aegis.log aegis: set +e; diff -U10 -a /aegis/hello_world/branch.1/branch.0/baseline/ hello_world/main.cpp /home/pruebas3/hello_world.1.0.C012/hello_world/ main.cpp > /home/pruebas3/hello_world.1.0.C012/hello_world/main.cpp,D; test $? -le 1 aegis: project "hello_world.1.0": change 12: difference complete Y finalizar el desarrollo: pruebas3@chupete:~/hello_world.1.0.C012> aede aegis: project "hello_world.1.0": change 12: development completed === Conclusiones === Más que un sistema de control de código fuente, Aegis permite implantar una metodología de desarrollo en un equipo de trabajo. El sistema exige que todos los cambios que se realicen en el software vayan acompañados de un test que los compruebe (y que deberá fallar en las versiones anteriores del mismo software). De esta forma, se pretende mejorar la calidad del software producido. El sistema además establece tres perfiles en el proceso de desarrollo de software: el desarrollador, que se ocupa fundamentalmente de realizar los cambios y los test que los acompañan, el revisor, que se ocupa de verificar la calidad de los desarrollos y los test realizados y el integrador, que da la revisión final antes de incorporarlos definitivamente al proyecto. Aegis se encarga de mantener siempre una "línea base" de proyecto que contiene todos los cambios realizados, probados, revisados e integrados, de tal modo que se trataría en todo momento de una versión "que funciona" y que de esta forma está siempre disponible para trabajar. Sin embargo, la metodología de trabajo que propone Aegis parece más adecuada para un entorno empresarial que para grupos de donde no hay una división tan clara del trabajo. Igualmente, se trata de un software ideal para aquellos grupos de trabajo que deseen implantar un método de trabajo así, pero en absoluto es adecuado para aquellos que tengan otros modelos de desarrollo. Es decir, que la herramienta exige de un cambio en la organización y forma de hacer el trabajo, y este hecho ha de ser tenido en cuenta a la hora de adoptarlo. Por otra parte, el hecho de que todo el trabajo deba hacerse on-line, tampoco favorece la dispersión geográfica de los miembros del equipo ya que implicaría que éstos debieran estar conectados al servidor continuamente para poder realizar los cambios. Como hecho muy favorable hay que comentar que, al exigir que siempre se realice un test para cada cambio que se haga, mejora enormemente la calidad del software que se produce; aunque por otra parte debería haber una forma de saltarse esta comprobación ya que hay errores que se detectan (un fallo de memoria, por ejemplo) que no siempre tienen un test que lo pueda evidenciar. La documentación no me ha parecido especialmente sencilla de leer, ya que hay que leersela prácticamente al completo para hacerse una idea de lo que el programa hace, y cómo hacerlo. Hay aspectos -como la creación del fichero de configuración- que son realmente oscuros, y no estaría de más un asistente o algo así para facilitar su creación. Este hecho se evidencia en que la curva de aprendizaje del programa es alta; por una parte todo se maneja en línea de comandos y los comandos para su uso están más pensados en el día a día que en facilitar la vida al que está aprendiendo a usar la herramienta. Además, la portabilidad hacia otras plataformas no es posible: no hay versión para otros sistemas operativos que no sea linux. ==== CVS ==== === Instalación === CVS ya está instalado en el sistema debian: chupete:~# apt-get install cvs Reading Package Lists... Done Building Dependency Tree... Done Sorry, cvs is already the newest version. 0 packages upgraded, 0 newly installed, 0 to remove and 1 not upgraded. == Creación del repositorio == Para ello seguiremos las instrucciones dadas en la documentación de esta asignatura, con la única diferencia que el repositorio lo crearemos en ''/var'' en lugar de en ''/var/lib'': chupete:/var# umask 0022 chupete:/var# mkdir cvs chupete:/var# chgrp src /var/cvs chupete:/var# chmod 3775 /var/cvs chupete:/var# cvs -d /var/cvs init == Haciendo una prueba sencilla == A fin de comprobar que el acceso en modo local al repositorio ya es posible, realizaremos la prueba de importar un fichero sólamente. Esta prueba la realizará el usuario "pruebas2", para lo cual habrá que añadirle al grupo de desarrolladores (src): chupete:~# usermod -G src pruebas2 Una vez hecho esto, el import funcionará perfectamente: pruebas2@chupete:~> rm -r prueba/ pruebas2@chupete:~> mkdir prueba pruebas2@chupete:~> echo "Esto es una prueba" > prueba/prueba.txt pruebas2@chupete:~>›> cd prueba pruebas2@chupete:~/prueba> setenv CVSROOT :local:/var/cvs pruebas2@chupete:~/prueba> setenv CVSEDITOR /usr/bin/joe pruebas2@chupete:~/prueba> cvs import prueba company_prueba start File /tmp/cvsMA4ExH saved. N prueba/prueba.txt No conflicts created by this import Comprobamos que efectivamente se obtienen bien los fuentes: pruebas2@chupete:~> mkdir prueba2 pruebas2@chupete:~> cd prueba2 pruebas2@chupete:~/prueba2> cvs co prueba cvs checkout: warning: cannot write to history file /var/cvs/CVSROOT/history: Permission denied cvs checkout: Updating prueba U prueba/prueba.txt pruebas2@chupete:~/prueba2> ls prueba pruebas2@chupete:~/prueba2> cd prueba pruebas2@chupete:~/prueba2/prueba> ls CVS prueba.txt pruebas2@chupete:~/prueba2/prueba> diff prueba.txt ../../prueba prueba/ prueba2/ pruebas2@chupete:~/prueba2/prueba> diff prueba.txt ../../prueba/prueba.txt El mensaje de error que aparece es porque los permisos de escritura para el fichero "history" no están adecuadamente puestos: chupete:/var/cvs/CVSROOT# ll total 92 drwxr-sr-x 2 root src 4096 Aug 2 20:38 Emptydir -rw-r--r-- 1 root src 0 Aug 2 20:38 history -rw-r--r-- 1 root src 0 Aug 2 20:38 val-tags chupete:/var/cvs/CVSROOT# chmod 664 history Tras este cambio de permisos, ya funciona correctamente. === Realizando el primer cambio === Al igual que en los otros casos, comenzaremos por realizar una importación del proyecto "hello world" que vamos a usar para los ejemplos y luego realizar un cambio en el mismo para reflejarlo en el repositorio. pruebas2@chupete:~/hello_world> cvs import -m "Creacion de directorio inicial" hello_world hello_world_company start N hello_world/Makefile.am N hello_world/Makefile.dist N hello_world/configure.in.in N hello_world/AUTHORS N hello_world/COPYING N hello_world/ChangeLog N hello_world/INSTALL N hello_world/README N hello_world/TODO N hello_world/hello_world.lsm N hello_world/hello_world.kdevprj N hello_world/hello_world/.deps/main.Po [....] cvs import: Importing /var/cvs/hello_world/autom4te.cache N hello_world/autom4te.cache/requests N hello_world/autom4te.cache/output.0 N hello_world/autom4te.cache/traces.0 No conflicts created by this import Ahora ya podemos eliminar el directorio "hello_world" y realizar el primer check-out directamente del repositorio de código fuente: pruebas2@chupete:~> rm -r hello_world pruebas2@chupete:~> ls aegis.conf hello_world.tgz prueba_orig pruebas2@chupete:~> cvs co hello_world cvs checkout: Updating hello_world U hello_world/AUTHORS U hello_world/COPYING U hello_world/ChangeLog [...] Ha funcionado correctamente. Ahora realizaremos un cambio en el fichero main.cpp: int main(int argc, char *argv[]) { cout << "Change made by pruebas2" << endl; cout << "Hello, World!" << endl; return EXIT_SUCCESS; } Y lo guardamos en el repositorio: pruebas2@chupete:~/hello_world/hello_world> cvs ci Checking in main.cpp; /var/cvs/hello_world/hello_world/main.cpp,v <-- main.cpp new revision: 1.2; previous revision: 1.1 done === Realizando un cambio concurrente === Ahora vamos a realizar un cambio en el repositorio usando dos usuarios que se conectarán simulando una conexión remota. Estos usuarios realizarán un cambio en el proyecto hello_world sobre una misma línea de código y veremos cómo trata cvs los cambios. == Haciendo que el repositorio sea visible públicamente == El acceso remoto de los desarrolladores se realizará a través de ssh, por lo que únicamente es necesario disponer de cuenta en la máquina. Para el propósito de esta evaluación hemos creado cuatro cuentas -''pruebas1''..''pruebas4''- a las que hemos asociado al grupo ''src'': chupete:~# usermod -G src pruebas1 chupete:~# usermod -G src pruebas2 chupete:~# usermod -G src pruebas3 chupete:~# usermod -G src pruebas4 chupete:~# more /etc/group | grep src src:x:40:pruebas2,pruebas1,pruebas3,pruebas4 No nos ocuparemos de habilitar el acceso público al repositorio porque no aportaría nada a la presente evaluación. Como el acceso que vamos a realizar es a través de ssh, ya no hay nada más que hacer porque el repositorio ya es accesible a través de estas cuentas de usuario. == Descargando el repositorio para trabajar con él == A continuación estableceremos en la máquina remota dos variables de entorno para informar a CVS de la ubicación del repositorio, y el editor que queremos usar para editar los archivos de log: pruebas1@chupete:~> setenv CVSROOT :ext:pruebas1@localhost:/var/cvs pruebas1@chupete:~> setenv CVSEDITOR /usr/bin/joe Y seguidamente haremos el "checkout": pruebas1@chupete:~> cvs co hello_world The authenticity of host 'localhost (127.0.0.1)' can't be established. RSA key fingerprint is 7b:cc:f5:ce:a7:ca:5e:3c:37:50:f9:f7:a3:59:4f:0a. Are you sure you want to continue connecting (yes/no)? Warning: Permanently added 'localhost' (RSA) to the list of known hosts. pruebas1@localhost's password: U hello_world/AUTHORS U hello_world/COPYING U hello_world/ChangeLog U hello_world/INSTALL [...] Aunque este ejemplo se ha realizado localmente por entero, la comunicación se ha realizado a través de ssh y podría haberse realizado igualmente por un usuario cualquiera accediendo remotamente a esta máquina. La misma seguencia de acciones la realizaremos para el usuario pruebas3. Hemos incorporado al fichero .cshrc las variables de entorno para que no haya que introducirlas en cada sesión: pruebas3@chupete:~> echo $CVSROOT :ext:pruebas3@localhost:/var/cvs pruebas3@chupete:~> echo $CVSEDITOR /usr/bin/joe pruebas3@chupete:~> cvs co hello_world The authenticity of host 'localhost (127.0.0.1)' can't be established. RSA key fingerprint is 7b:cc:f5:ce:a7:ca:5e:3c:37:50:f9:f7:a3:59:4f:0a. Are you sure you want to continue connecting (yes/no)? Warning: Permanently added 'localhost' (RSA) to the list of known hosts. pruebas3@localhost's password: cvs server: Updating hello_world U hello_world/AUTHORS U hello_world/COPYING U hello_world/ChangeLog U hello_world/INSTALL [....] == Efectuando un cambio concurrente == Tanto el usuario pruebas3 como el usuario pruebas4 harán un cambio en la misma línea de código del fichero main.cpp, y comprobaremos lo que ocurre al guardar los cambios en el repositorio. pruebas2@chupete:~/hello_world/hello_world> joe main.cpp int main(int argc, char *argv[]) { cout << "Hello, World! + change made by pruebas2" << endl; return EXIT_SUCCESS; pruebas3@chupete:~/hello_world/hello_world> joe main.cpp int main(int argc, char *argv[]) { cout << "Hello, World! + CHANGE MADE BY PRUEBAS3" << endl; return EXIT_SUCCESS; el usuario pruebas2 guarda sus cambios: pruebas2@chupete:~/hello_world/hello_world> cvs ci -m "Change on main.cpp" cvs commit: Examining . cvs commit: Examining .deps cvs commit: Examining docs cvs commit: Examining docs/en cvs commit: Examining templates Checking in main.cpp; /var/cvs/hello_world/hello_world/main.cpp,v <-- main.cpp new revision: 1.5; previous revision: 1.4 done el usuario pruebas3 guarda sus cambios: pruebas3@chupete:~/hello_world/hello_world> cvs ci -m "Change on main.cpp" cvs commit: Examining . cvs commit: Examining .deps cvs commit: Examining docs cvs commit: Examining docs/en cvs commit: Examining templates pruebas3@localhost's password: cvs server: Up-to-date check failed for 'main.cpp' cvs [server aborted]: correct above errors first! e informa que se ha producido un error al actualizar el fichero "main.cpp". Al ejecutar update para que actualice su contenido: pruebas3@chupete:~/hello_world/hello_world> cvs update pruebas3@localhost's password: cvs server: Updating . M Makefile RCS file: /var/cvs/hello_world/hello_world/main.cpp,v retrieving revision 1.4 retrieving revision 1.5 Merging differences between 1.4 and 1.5 into main.cpp rcsmerge: warning: conflicts during merge cvs server: conflicts found in main.cpp C main.cpp cvs server: Updating .deps cvs server: Updating docs M docs/Makefile cvs server: Updating docs/en M docs/en/Makefile cvs server: Updating templates Nos informa que ha habido conflictos en el fichero y que se han realizado mezclas de código. Al examinar el código podemos observar que cvs ha puesto los cambios del otro usuario y los nuestros juntos: int main(int argc, char *argv[]) { <<<<<<< main.cpp cout << "Hello, World! + CHANGE MADE BY PRUEBAS3" << endl; ======= cout << "Hello, World! + change made by pruebas2" << endl; >>>>>>> 1.5 return EXIT_SUCCESS; } === Conclusiones === CVS es uno de los sistemas de control de código fuente más utilizados en la actualidad. Quizá su mayor virtud es la estabilidad, y la gran cantidad de herramientas disponibles para utilizarlo. También cuenta con una comunidad de usuarios muy activa y con versiones portadas a diferentes plataformas. Sin embargo, quizá su mayor defecto es la poca flexibilidad que ofrece en el manejo del repositorio, ya que el borrado de directorios no está permitido, y el soporte que ofrece para los ficheros binarios es deficiente. No obstante, se trata de un sistema muy interesante por lo ampliamente probado que está, y la posibilidad que ofrece de trabajar desde otras plataformas. En el terreno de la documentación, el documento clásico sobre cvs -el Cederqvist- me ha parecido bastante arduo de leer, ya que exige leerlo por completo para encontrar cosas que deberían estar explicadas al principio. Se hace necesario otro manual que explique el uso y administración de la herramienta de una forma más fácil. ==== Subversion ==== === Instalación === Subversion está disponible en paquete debian sólamente para la versión "sarge", que es la versión estable en la actualidad. Aunque existía para woody (Debian 3.0 r2), su autor ha descontinuado el producto una vez que "sarge" pasó a estable. Por lo tanto, la única opción que queda es compilar el paquete y realizar una instalación manual. Hemos descargado el la versión 1.2.1, que funciona sin Berkeley DB. Tras ejecutar configure -hemos de tener en cuenta que configure "llama" a otros "configure" de sub-proyectos que se subversion incorpora para que pueda funcionar- pasamos a ejecutar make; make install (este último comando como root), y el proceso de instalación finaliza sin problemas. == Creación del repositorio == El siguiente paso consiste en la creación del repositorio, que ubicaremos en el directorio ''/var/svn'': chupete:~# svnadmin create /var/svn/ chupete:~# ls /var/svn README.txt conf dav db format hooks locks chupete:~# more /var/svn/README.txt This is a Subversion repository; use the 'svnadmin' tool to examine it. Do not add, delete, or modify files here unless you know how to avoid corrupting the repository. If the directory "db" contains a Berkeley DB environment, you may need to tweak the values in "db/DB_CONFIG" to match the requirements of your site. Visit http://subversion.tigris.org/ for more information. **Securización de subversion** Nuestra intención al utilizar subversion en modo local es que un grupo de usuarios determinado tuviera acceso al repositorio y a la operativa de subversion, mientras que otro grupo de usuarios, aunque tuviera cuenta en el sistema, no pudiera utilizarlo. Bajo estas premisas, procedimos a asignar permisos de forma que sólo aquellos usuarios que pertenecieran al grupo de usuarios ''svn'' tuvieran acceso al repositorio. Para ello, creamos un grupo ''svn'', al que añadimos a dos usuarios que nos servirían para hacer las pruebas: chupete:/var# groupadd svn chupete:/var# usermod -G svn rluna chupete:/var# usermod -G svn pruebas1 Los permisos sobre el directorio serán 2770 -el "2" es el bit group-id, y tiene por objeto que cada fichero o directorio que se cree dentro de ese directorio pertenezca al grupo al que pertenece el directorio-, y el grupo será svn: chupete:~# chmod 2770 /var/svn chupete:~# chgrp svn /var/svn Los comandos relacionados con subversion sólo podrán ser ejecutados por root o los miembros del grupo svn: chupete:~# ls -la /usr/local/bin/svn* -rwxr-xr-x 1 root root 360019 Aug 8 17:22 /usr/local/bin/svn -rwxr-xr-x 1 root root 101569 Aug 8 17:23 /usr/local/bin/svnadmin -rwxr-xr-x 1 root root 89589 Aug 8 17:23 /usr/local/bin/svndumpfilter -rwxr-xr-x 1 root root 121222 Aug 8 17:23 /usr/local/bin/svnlook -rwxr-xr-x 1 root root 150360 Aug 8 17:23 /usr/local/bin/svnserve -rwxr-xr-x 1 root root 77564 Aug 8 17:23 /usr/local/bin/svnversion chupete:~# chgrp svn /usr/local/bin/svn* chupete:~# chmod 550 /usr/local/bin/svn* # el comando "svn" debe tener permisos de ejecución globales (555) debido # a que todos los usuarios deberán tener acceso al mismo (por si quieren # utilizar Subversion como cliente) chupete:~# chmod 555 /usr/local/bin/svn chupete:~# ls -la /usr/local/bin/svn* -r-xr-xr-x 1 root svn 360019 Aug 8 17:22 /usr/local/bin/svn -r-xr-x--- 1 root svn 101569 Aug 8 17:23 /usr/local/bin/svnadmin -r-xr-x--- 1 root svn 89589 Aug 8 17:23 /usr/local/bin/svndumpfilter -r-xr-x--- 1 root svn 121222 Aug 8 17:23 /usr/local/bin/svnlook -r-xr-x--- 1 root svn 150360 Aug 8 17:23 /usr/local/bin/svnserve -r-xr-x--- 1 root svn 77564 Aug 8 17:23 /usr/local/bin/svnversion A continuación, crearemos el repositorio mediante el comando svnadmin. El repositorio lo creará el usuario root también, para que los permisos sean lo más uniformes posible: chupete:/var/svn# svnadmin create /var/svn chupete:/var/svn# ls README.txt conf dav db format hooks locks chupete:/var/svn# more README.txt This is a Subversion repository; use the 'svnadmin' tool to examine it. Do not add, delete, or modify files here unless you know how to avoid corrupting the repository. If the directory "db" contains a Berkeley DB environment, you may need to tweak the values in "db/DB_CONFIG" to match the requirements of your site. A continuación, y para permitir que cualquier miembro del grupo "svn" tenga acceso al mismo, cambiaremos el grupo de acceso para todos los ficheros del repositorio: chupete:/var/svn# chgrp svn * -R chupete:/var/svn# ls -la total 36 drwxrwx--- 7 root svn 4096 Aug 8 20:16 . drwxr-xr-x 19 root root 4096 Aug 8 19:04 .. -rw-r--r-- 1 root svn 379 Aug 8 20:16 README.txt drwxr-xr-x 2 root svn 4096 Aug 8 20:16 conf drwxr-xr-x 2 root svn 4096 Aug 8 20:16 dav drwxr-sr-x 5 root svn 4096 Aug 8 20:16 db -r--r--r-- 1 root svn 2 Aug 8 20:16 format drwxr-xr-x 2 root svn 4096 Aug 8 20:16 hooks drwxr-xr-x 2 root svn 4096 Aug 8 20:16 locks Cambios de permisos: chupete:/var/svn# chmod g+w conf dav db hooks/ locks/ chupete:~# chmod 600 /var/svn/conf/passwd chupete:~# chmod 600 /var/svn/conf/svnserve.conf chupete:/var/svn/db# chmod g+w transactions/ revprops/ revs/ chupete:/var/svn/db# chmod 664 current fs-type uuid write-lock chupete:/var/svn/db/revprops# chmod 664 0 chupete:/var/svn/db/revs# chmod 664 0 chupete:/var/svn/hooks# chmod 664 *tmpl chupete:/var/svn/locks# chmod 664 *lock Seguidamente comprobaremos que el acceso al repositorio está permitido haciendo que uno de los usuarios haga una importación de un proyecto: pruebas1@chupete:~> svn import hello_world file:///var/svn/hello_world -m 'Initial release' Adding hello_world/Makefile.dist Adding hello_world/configure Adding hello_world/Makefile.in Adding hello_world/configure.files [....] Adding hello_world/aclocal.m4 Adding hello_world/Makefile Adding hello_world/hello_world.kdevses Adding hello_world/hello_world.kdevprj Adding hello_world/stamp-h.in Committed revision 1. Se ha comprobado que el usuario pruebas1 como otro usuario que pertenezca al grupo ''svn'' tiene ahora permisos para importar proyectos, hacer check-out y check-in de modificaciones, borrar entradas, etc, por lo que los permisos que se han asignado funcionan con normalidad y permiten la operacion del sistema. Quedaría comprobar que aquellos usuarios que no pertenecen a ese grupo especial no disponen de los permisos adecuados para ello: pruebas2@chupete:~> svnadmin create /home/pruebas2/myrepo /usr/local/bin/svnadmin: Permission denied. pruebas2@chupete:~> svn import prueba_orig/ file:///var/svn/prueba_orig -m 'prueba de permisos' svn: Unable to open an ra_local session to URL svn: Unable to open repository 'file:///var/svn/prueba_orig' svn: Can't open file '/var/svn/prueba_orig/format': Permission denied === Realizando del primer cambio === Nuestro primer paso será, como viene siendo habitual, realizar una importación del proyecto "hello_world" que estamos usando en todos los ejemplos y posteriormente realizaremos un cambio en el mismo para reflejarlo en el repositorio. pruebas1@chupete:~> svn import hello_world file:///var/svn/hello_world -m 'Initial release' Adding hello_world/Makefile.dist Adding hello_world/configure [....] Adding hello_world/hello_world.kdevses Adding hello_world/hello_world.kdevprj Adding hello_world/stamp-h.in Committed revision 1. El siguiente paso será hacer el checkout inicial: pruebas1@chupete:~> rm -r hello_world/ pruebas1@chupete:~> svn co file:///var/svn/hello_world hello_world/ A hello_world/Makefile.dist A hello_world/configure A hello_world/Makefile.in A hello_world/configure.files [...] A hello_world/subdirs A hello_world/aclocal.m4 A hello_world/Makefile A hello_world/hello_world.kdevses A hello_world/hello_world.kdevprj A hello_world/stamp-h.in Checked out revision 1. Realizar un cambio en uno de los archivos: pruebas1@chupete:~> joe hello_world/AUTHORS Raul Luna Rodriguez pruebas1 Y hacer un commit: pruebas1@chupete:~/hello_world> svn ci -m 'Changed AUTORS file to include myself' Sending AUTHORS Transmitting file data . Committed revision 2. Y posteriormente actualizar los cambios del servidor: pruebas1@chupete:~/hello_world> svn update At revision 2. === Realizando un cambio concurrente === Igual que en las pruebas anteriores, comprobaremos cómo resuelve subversion las colisiones en caso de que dos usuarios realicen un cambio sobre la misma línea de un archivo de código fuente. Para realizar las pruebas simularemos que el acceso se está realizando remotamente al repositorio, aunque las pruebas se seguirán haciendo en local. == Haciendo que el repositorio sea visible públicamente == El acceso público lo efectuaremos habilitando el protocolo propietario para el acceso remoto a la aplicación; svnserve, desde el super-demonio de internet, xinetd. **Creando un usuario no privilegiado** Nuestro primer paso -que no lo dice la documentación- será crear un usuario no privilegiado para que sea quien acceda al repositorio: chupete:~# useradd -c 'Subversion unpriviledged user' -d /var/svn/ -g svn -G svn -s /bin/false svn chupete:~# passwd svn Enter new UNIX password: Retype new UNIX password: passwd: password updated successfully Y procederemos a cambiar el propietario de todos los ficheros del repositorio: chupete:~# chown svn /var/svn -Rv changed ownership of /var/svn' to svn changed ownership of /var/svn/dav' to svn changed ownership of /var/svn/locks' to svn changed ownership of /var/svn/locks/db.lock' to svn changed ownership of /var/svn/locks/db-logs.lock' to svn changed ownership of /var/svn/hooks' to svn [...] **Configurando el super-demonio de internet** El siguiente paso será configurar el super-demonio de internet para que ejecute el servidor de subversion, svnserve. Para ello comenzaremos por añadir en /etc/services los puertos asignados por la IANA para subversion: chupete:~# joe /etc/services [....] mysql 3306/tcp # MySQL mysql 3306/udp # MySQL svn 3690/tcp # Subversion svn 3690/udp # Subversion rfe 5002/tcp # Radio Free Ethernet rfe 5002/udp # Actually uses UDP only [....] Para luego configurar xinetd para que ejecute el servidor svnserve cuando se le requiera: chupete:~# joe /etc/xinetd.conf # Subversion service svn { socket_type = stream protocol = tcp wait = no user = svn server = /usr/local/bin/svnserve server_args = -i } **Configurar el servidor de subversion** Para probar que nuestra configuración de acceso funciona, habilitaremos el acceso anónimo de lectura al repositorio editando el fichero ''/var/svn/conf/svnserve.conf'': chupete:/var/svn/conf# joe svnserve.conf [general] ### These options control access to the repository for unauthenticated ### and authenticated users. Valid values are "write", "read", ### and "none". The sample settings below are the defaults. anon-access = read auth-access = write ### The password-db option controls the location of the password ### database file. Unless you specify a path starting with a /, ### the file's location is relative to the conf directory. ### Uncomment the line below to use the default password file. # password-db = passwd ### This option specifies the authentication realm of the repository. ### If two repositories have the same authentication realm, they should ### have the same password database, and vice versa. The default realm ### is repository's uuid. realm = Project TAS repository También es necesario configurar adecuadamente el fichero passwd que se encuentra en el mismo directorio: chupete:/var/svn/conf# more passwd ### The name and password for each user follow, one account per line. [users] rluna=pepinillo **Una prueba sencilla** Y finalmente, haremos una prueba sencilla para conectarnos a través de la red, pero de modo local: rluna@chupete:~$ svn co svn://localhost/var/svn/hello_world hello_world A hello_world/Makefile.dist A hello_world/configure A hello_world/Makefile.in A hello_world/configure.files [....] A hello_world/Makefile A hello_world/hello_world.kdevses A hello_world/hello_world.kdevprj A hello_world/stamp-h.in Checked out revision 2. **Afinando la configuración ** Se puede mejorar la configuración del super-demonio de internet estableciendo la máscara adecuada para el usuario que lo ejecuta (002) y un grupo por defecto -svn, que ya venía configurado para el usuario-. También es interesante pasar el parámetro -r al servidor, para que haga un chroot al repositorio y de esta forma en la url no es necesario indicar la ruta al repositorio. Es decir, que en vez de referirnos a nuestro proyecto como ''svn://localhost/var/svn/hello_world'' ahora nos podemos referir como ''svn://localhost/hello_world'', que es mucho más intuitivo. La configuración ''xinetd.conf'' quedará como sigue: chupete:/etc# joe xinetd.conf # Subversion service svn { socket_type = stream protocol = tcp wait = no user = svn group = svn umask = 002 server = /usr/local/bin/svnserve server_args = -i -r /var/svn } Y podremos comprobar que la prueba sigue funcionando (tras reiniciar el super-demonio de internet): rluna@chupete:~$ svn co svn://localhost/hello_world hello_world A hello_world/Makefile.dist A hello_world/configure A hello_world/Makefile.in A hello_world/configure.files A hello_world/AUTHORS [....] A hello_world/Makefile A hello_world/hello_world.kdevses A hello_world/hello_world.kdevprj A hello_world/stamp-h.in Checked out revision 2. == Descargando el repositorio para trabajar con él == Ahora efectuaremos una descarga del repositorio para el usuario "pruebas1" y para el usuario "pruebas2" (este último se ha añadido al repositorio de subversion): pruebas2@chupete:~> svn co svn://localhost/hello_world hello_world A hello_world/Makefile.dist A hello_world/configure A hello_world/Makefile.in A hello_world/configure.files [....] A hello_world/hello_world.kdevses A hello_world/hello_world.kdevprj A hello_world/stamp-h.in Checked out revision 6. == Efectuando un cambio concurrente== Ahora, tanto el usuario pruebas1 como pruebas2 modificarán el fichero main.cpp: pruebas1@chupete:~/hello_world/hello_world> joe main.cpp int main(int argc, char *argv[]) { cout << "Hello, World! + change made by pruebas1" << endl; return EXIT_SUCCESS; pruebas2@chupete:~/hello_world/hello_world> joe main.cpp int main(int argc, char *argv[]) { cout << "Hello, World! + CHANGE MADE BY PRUEBAS2" << endl; return EXIT_SUCCESS; } Primero pruebas1 hace el commit: pruebas1@chupete:~/hello_world/hello_world> svn ci -m 'change made by pruebas1' Authentication realm: Project TAS repository Password for 'pruebas1': Sending hello_world/main.cpp Transmitting file data . Committed revision 7. La contraseña que nos ha solicitado no es la de acceso al sistema de pruebas1, sino la de acceso al repositorio, que es diferente. A continuación efectuaremos el commit para el usuario pruebas2: pruebas2@chupete:~/hello_world/hello_world> svn ci -m 'change made by pruebas2' Authentication realm: Project TAS repository Password for 'pruebas2': Sending hello_world/main.cpp Transmitting file data .svn: Commit failed (details follow): svn: Out of date: '/hello_world/hello_world/main.cpp' in transaction '7-1' Subversion informa que el fichero main.cpp está "out of date", es decir, está obsoleto según la última revisión. Procedemos entonces a actualizarlo: pruebas2@chupete:~/hello_world/hello_world> svn update C main.cpp Updated to revision 7. y nos informa que ha habido un conflicto con main.cpp. Subversion crea entonces tres archivos con extensiones .mine, .rANTERIOR y .rACTUAL. El primero es el fichero original, el siguiente es el fichero tal y como estaba en el repositorio antes del cambio y finalmente el fichero .rACTUAL es el fichero como está actualmente en el repositorio. Lo ilustraremos con el propio contenido del fichero: pruebas2@chupete:~/hello_world/hello_world> tail main.cpp int main(int argc, char *argv[]) { <<<<<<< .mine cout << "Hello, World! + CHANGE MADE BY PRUEBAS2" << endl; ======= cout << "Hello, World! + change made by pruebas1" << endl; >>>>>>> .r7 return EXIT_SUCCESS; } pruebas2@chupete:~/hello_world/hello_world> tail main.cpp.mine #include #include int main(int argc, char *argv[]) { cout << "Hello, World! + CHANGE MADE BY PRUEBAS2" << endl; return EXIT_SUCCESS; } pruebas2@chupete:~/hello_world/hello_world> tail main.cpp.r6 #include #include int main(int argc, char *argv[]) { cout << "Hello, World!" << endl; return EXIT_SUCCESS; } pruebas2@chupete:~/hello_world/hello_world> tail main.cpp.r7 #include #include int main(int argc, char *argv[]) { cout << "Hello, World! + change made by pruebas1" << endl; return EXIT_SUCCESS; } En este caso, haremos prevalecer nuestro cambio sobre el del otro usuario: pruebas2@chupete:~/hello_world/hello_world> mv main.cpp.mine main.cpp pruebas2@chupete:~/hello_world/hello_world> rm main.cpp.r* pruebas2@chupete:~/hello_world/hello_world> svn commit -m 'change made by pruebas2 that superseedes change made by pruebas1' Sending hello_world/main.cpp Transmitting file data . Committed revision 8. === Conclusiones === La razón de ser de Subversion es ofrecer a la comunidad de Open Source "un CVS mejorado". En este sentido, ofrece características que CVS no tiene, como son: * posibilidad de versionar directorios * posibilidad de incluir enlaces simbólicos * ahora es posible renombrar un fichero sin que éste pierda su historia * ahora es posible crear un fichero a partir de la copia de otro sin que éste pierda su historia * ahora se pueden borrar directorios * un mejor control sobre los formatos binarios Quizá los requerimientos de seguridad, tanto en el acceso al repositorio a través de un acceso local al propio sistema como a través del protocolo svnserve sea la parte que presenta más carencias. Por la primera parte, el procedimiento de instalación no establece un conjunto de permisos claro que permita que un grupo de usuarios de la máquina pueda trabajar con el repositorio y otros no, por otra parte el acceso remoto no puede limitar el acceso a partes del repositorio a no ser que se utilicen los "hooks" que provee la aplicación. Los permisos sí que resultan válidos en el segundo caso para proyectos únicos, pero no para el caso en que un servidor aloje a varios proyectos. La documentación que se ha consultado, "Version Control with Subversion", es muy completa y amena de leer; incluye una guía para hacerse rápidamente con la aplicación, y también un capítulo dedicado a la administración, aparte de una guía de referencia. Como dicen los autores, responde a las necesidades de conocimiento de subversion que han ido surgiendo en las listas de correo de esta aplicación a lo largo del tiempo. ==== Conclusión final ==== Como conclusión final de este análisis de varios sistemas de control de código fuente, puede decirse que a pesar de lo superficial del análisis, he podido detectar diferencias importantes entre las aplicaciones analizadas. Quizá los análisis expuestos aquí no hayan sido de la profundidad deseable; quedarían por tratar numerosos puntos como son la facilidad de administración, un análisis en profundidad de la seguridad de la solución presentada, la estabilidad del repositorio ante posibles daños en el mismo, la disponibilidad de API's o librerías para crear aplicaciones cliente. Incluso sería muy interesante analizar la disponibilidad de estas herramientas para otras plataformas -Windows o Mac-. No obstante, para poder realizar una mínima operativa con los repositorios, ha sido necesario en muchos casos leerse completamente el manual del programa, por lo que sí que se ha obtenido una visión en profundidad de los puntos fuertes y débiles de cada programa. Antes de continuar con el análisis definitivo, quisiera dar un breve repaso de las características más importantes de cada uno de estos sistemas de control de código fuente: En primer lugar, decir que **GNU arch** y **Open CM** no se pudieron instalar en la versión de debian que estoy utilizando en la actualidad -Debian 3.0 Woody 1-, por lo que quedaron fuera de la evaluación. No obstante, OpenCM tiene características muy interesantes respecto a seguridad y robustez de la solución que merecería la pena valorar. **Respecto a darcs**, los dos aspectos más destacables son por un lado la sencillez de su funcionamiento: el repositorio se crea en el propio directorio de desarrollo, con lo que tenemos control de código fuente desde las fases primarias del desarrollo. Más adelante, cuando el proyecto va avanzando y necesitamos disponer de un repositorio en internet con acceso público, el manual nos dice cómo hacerlo de la forma más fácil posible. Finalmente, cuando otros desarrolladores crean parches que nos interesan -también con darcs- podemos fácilmente pasar a formar una comunidad de desarrolladores sincronizando nuestros respectivos repositorios. Esta naturaleza distribuida de los repositorios es un concepto interesante, que simplifica mucho la tarea de compartir parches y desarrollos entre desarrolladores, sin embargo encontramos que la carencia de un unico punto centralizado donde se guarden los fuentes hace que se pierda -o quede dificultada enormemente- el desarrollo de una versión "oficial", cuando cualquiera de los desarrolladores puede tener la suya propia en su máquina, mejor o peor parcheada en función de su conexión a internet. **Respecto a Aegis** puede considerarse que es algo más que un sistema de control de código fuente. Además de ello, permite organizar el trabajo al establecer varios perfiles de acceso a la aplicación: desarrollador, revisor, integrador y administrador. Por otra parte, en el proceso de desarrollo se implanta la obligatoriedad de incluir un script que compruebe cada cambio que se implanta, con lo que se mejora la calidad del software que se produce. Las ventajas que añade Aegis en el desarrollo de su solución se transforman en inconvenientes en un sistema de fuente abierta, donde los desarrolladores realizan su labor "por amor al arte", y es difícil que encajen favorablemente tener a alguien por encima que les asigne las tareas y que revise su trabajo. Sin embargo sí que parece un sistema muy apropiado para un entorno empresarial, donde esas jerarquías suelen estar fuertemente establecidas, y donde por otra parte la disponibilidad de acceso a la máquina de desarrollo es total, hecho que se hace necesario en este tipo de herramientas y que me parece un requisito muy exigente para llevarlo a la práctica en el mundo del software libre. **La opción de CVS** nos parece altamente interesante por varios motivos: 1) se trata de un software ampliamente probado, y del que se puede disponer incluso de empresas que ofrecen soporte; 2) hay una comunidad de usuarios muy activa que pueden ofrecernos su ayuda, y abundante documentación; 3) la disponbilidad en otras plataformas -por lo menos en Windows- está ampliamente consolidada, con multitud de herramientas tanto cliente como servidor. Sin embargo, las carencias que tiene en la gestión del repositorio son destacables: la dificultad para almacenar binarios, la prácticamente imposibilidad de borrar directorios, o de almacenar elementos como los enlaces simbólicos, el que la historia de un fichero se mantenga aunque borremos completamente ese fichero y creemos uno nuevo con el mismo nombre.... Se trata de carencias que han alentado la creación del siguiente competidor en la lucha, Subversion. **Subversion nace** --y así lo declaran abiertamente sus creadores-- como ''un CVS mejorado''. Es decir; funcionar más o menos igual que CVS, pero con la posibilidad de hacer aquello que CVS nunca permitió: guardar historia de un directorio, permitir cambiar de nombre elementos como directorios, gestión eficiente de archivos binarios mediante una herramienta "diff" que soporta comparación en modo binario; contemplar la posibilidad de tratar las colisiones para los ficheros binarios también, permitir reemplazar ficheros.... Precísamente esta característica de "caballo ganador" que presenta Subversion, ha hecho que nos tomemos algún tiempo más en su análisis, leyendo en detalle el manual de la aplicación. Hemos encontrado que, por lo menos en la versión que se instala en Debian 3.0 (woody1) existen importantes deficiencias de seguridad: no está adecuadamente cubierto el acceso a través del sistema de ficheros. Por otra parte, el programa servidor, svnserve debería ejecutarse como root, comprometiendo la seguridad del sistema en caso de que se produjera una deficiencia de seguridad como un buffer overflow. **Finalmente** Si tuviera que decidirme por un sistema de control de código fuente para un proyecto de software libre, optaría por Subversion, haciendo las modificaciones de controles de acceso oportunas. En su favor obra la gran difusión que está teniendo, la calidad del repositorio que implementan, la fortaleza de su diseño que le va a permitir un crecimiento estable en los próximos años, y el hecho innegable que es el reemplazo para CVS, por lo que va a tener una gran difusión en los próximos años, y eso garantiza la buena salud del proyecto.