User Tools

Site Tools


projects:uocmaster

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:

  1. La tecla “AltGr” no está. En el lugar donde debería estar, encontramos la tecla /o/a (primero, primera)
  2. Las teclas de mayor que y menor que, tampoco están
  3. 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 <fred@bloggs.invalid>'.  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 <pruebas2@tas.raulluna.net>
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 <pruebas2@tas.raulluna.net>
  * Initial Release
Shall I pull this patch? (1/2) [ynWvpxqadjk], or ? for help: y

Sat Jul 23 18:23:02 CEST 2005  Pruebas Darcs <pruebas2@tas.raulluna.net>
  * 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 <pruebas2@tas.raulluna.net>
  * 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 <fred@bloggs.invalid>'.  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 <pruebas1@tas.raulluna.net>
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 <pruebas2@tas.raulluna.net>
  * 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 <rluna@tas.raulluna.net>
--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 <rluna@tas.raulluna.net>
pruebas1 <pruebas1@tas.raulluna.net>

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;
<code>
 
<code shell>
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: <svn://localhost:3690> 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: <svn://localhost:3690> 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 <iostream.h>
#include <stdlib.h>
 
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 <iostream.h>
#include <stdlib.h>
 
int main(int argc, char *argv[])
{
  cout << "Hello, World!" << endl;
 
  return EXIT_SUCCESS;
}
pruebas2@chupete:~/hello_world/hello_world> tail main.cpp.r7
 
#include <iostream.h>
#include <stdlib.h>
 
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.

projects/uocmaster.txt · Last modified: 2014/12/24 10:42 (external edit)