Gestión de
versiones con
CVS y
Subversion
Acerca de este documento
El uso de un gestor de versiones se vuelve imprescindible para evitar la tediosa tarea de intercambiar entre los programadores los ficheros de código fuente que componen un proyecto según estos ficheros se van actualizando. Este documento intenta recopilar los conocimientos necesarios para usar y administrar un gestor de versiones.
En concreto, la primera parte de este documento, que corresponde a los dos primeros temas, recopila los elementos generales que incorporan los gestores de versiones. La segunda parte de este documento incluye los temas tercero al sexto donde se explica el manejo de CVS, posiblemente el gestor de versiones más utilizado en la actualizad. La tercera parte de este documento incluye a los temas séptimo a noveno, y estudia el manejo de Subversion, el otro gran gestor de versiones que está ganando rápidamente popularidad. En consecuencia, la primera parte de este documento deberá ser leída antes de abarcar las otras dos. Si el lector ha decidido ya qué gestor de versiones desea usar, puede omitir la lectura de la segunda o de la tercera parte.
Al acabar este documento esperamos que el lector haya adquirido los conocimientos necesarios para ser capaz de instalar, usar y administrar su propio gestor de versiones.
Nota legal
Este tutorial ha sido escrito por Fernando López Hernández para MacProgramadores, y de acuerdo a los derechos que le concede la legislación española e internacional el autor prohíbe la publicación de este documento en cualquier otro servidor web, así como su venta, o difusión en cualquier otro medio sin autorización previa.
Sin embargo el autor anima a todos los servidores web a colocar enlaces a este documento. El autor también anima a cualquier persona interesada en conocer el funcionamiento de CVS y Subversion a bajarse o imprimirse este tutorial.
Klagenfurt, Septiembre del 2007 Para cualquier aclaración contacte con:
Tabla de Contenido
TEMA 1: Qué es un gestor de versiones
1. Introducción ... 8
2. Por qué usar un repositorio ... 8
3. Revisiones, versiones release y variantes... 9
4. Modelos de configuración... 9
5. Versionado extensional e intensional... 10
6. Gestores de versiones orientados a ficheros y a proyectos ... 10
TEMA 2: Elementos del repositorio 1. Interacción con el repositorio ... 13
2. Resolución de conflictos ... 15 3. Tagging... 15 4. Branching ... 16 4.1. Números de revisión ... 16 4.2. Mezclar ramas ... 17 4.3. Estrategias de branching ... 18 4.4. Tipos de ramas ... 19
5. Ficheros de texto y ficheros binarios ... 20
6. Herramientas de productividad ... 21
7. Hook scripts... 21
8. Repositorios distribuidos ... 22
TEMA 3: Guía rápida de CVS 1. Instalación... 24
2. Crear el repositorio... 24
3. Acceso a repositorios remotos ... 25
4. Importar un proyecto ... 26
5. Crear el sandbox ... 27
6. Bajar cambios del repositorio... 28
7. Enviar cambios al repositorio... 29
8. Añadir ficheros ... 30
9. Borrar ficheros ... 31
TEMA 4: CVS desde el punto de vista del usuario 1. El cliente de CVS ... 33
1.1. Opciones comunes ... 33
1.2. Permisos de fichero... 34
2. Mantener actualizado el repositorio... 34
2.1. Subir ficheros al repositorio... 34
2.2. Bajar ficheros del repositorio... 35
3. Obtener información sobre el proyecto... 36
3.1. Estado de los ficheros ... 36
3.2. Evolución histórica de los ficheros ... 38
3.4. Procedencia de las líneas ... 41
4. Obtener revisiones anteriores ... 41
4.1. Recuperar código anterior... 42
4.2. Deshacer cambios ... 43
4.3. Recuperar ficheros eliminados... 44
4.4. Obtener revisiones por fecha ... 45
5. Mezclas y conflictos ... 47
6. Mantenimiento de ficheros del repositorio... 49
6.1. Añadir ficheros y directorios al repositorio ... 49
6.2. Borrar ficheros y directorios del repositorio ... 50
6.3. Mover ficheros y directorios ... 51
7. Exportar ficheros ... 52
8. Liberar el sandbox... 53
9. Los patch de proyecto ... 53
9.1. Crear un fichero de patch ... 54
9.2. Aplicar el fichero de patch... 55
9.3. Crear un fichero de patch con CVS ... 56
10. Ficheros binarios y wrappers ... 56
11. Opciones de comando por defecto ... 57
TEMA 5: Tagging y branching 1. Tagging... 60
1.1. Tagging en el sandbox ... 60
1.2. Tagging en el repositorio ... 61
1.3. Obtener ficheros etiquetados ... 62
1.4. Borrar y mover tags ... 62
1.5. Borrar o mover tags de ficheros borrados... 64
1.6. Renombrar tags ... 64
1.7. Stickiness ... 64
2. Branching ... 65
2.1. Crear una rama ... 66
2.2. Activar la rama en el sandbox ... 68
2.3. Ramas retroactivas... 69
2.4. Ramas en revisiones anteriores ... 69
2.5. Añadir y borrar ficheros ... 70
2.6. Mezclar ramas ... 70
2.7. Deshacer la aplicación de una rama... 75
2.8. Borrar o mover una rama ... 76
2.9. Vendor branches... 76
TEMA 6: CVS desde el punto de vista del administrador 1. Seguridad en el repositorio... 79
1.1. Permisos en el sandbox ... 79
1.2. Permisos en el repositorio... 79
1.3. El repositorio y el directorio CVSROOT ... 81
2. Acceso remoto al repositorio con pserver ... 83
2.1. Activar el servicio... 83
2.2. El fichero passwd ... 83
2.3. Logarse en CVS ... 84
2.5. Crear una cuenta anónima... 86
2.6. Consideraciones de seguridad ... 87
3. Configuración del cliente ... 87
3.1. Ficheros de configuración en el sandbox ... 88
3.2. Ficheros de configuración en el directorio home ... 88
3.3. Variables de entorno ... 89
4. Configuración del servidor... 89
4.1. Acceso a los ficheros de CVSROOT ... 89
4.2. Ficheros de configuración ... 90
4.3. Hook scripts ... 91
TEMA 7: Guía rápida de Subversion 1. Características de Subversion ... 94
2. Instalación... 95
3. Configuración... 96
3.1. Ejecutar como un demonio ... 96
3.2. Ejecutar con inetd o xinetd ... 97
Tunneling sobre SSH ... 98
4. Crear el repositorio... 98
5. Importar un proyecto ... 98
6. Crear la working copy ...100
7. Acceso a repositorios remotos ...101
8. Commit y update...102
9. Estado de los ficheros de la working copy...103
TEMA 8: Subversion desde el punto de vista del usuario 1. El cliente de Subversion ...105
2. Exportar un proyecto ...106
3. Layout del repositorio ...106
4. Mantener actualizada la working copy ...108
5. Modificar el proyecto ...110
5.1. El editor por defecto...111
5.2. Añadir ficheros al proyecto ...111
5.3. Borrar ficheros del proyecto ...112
5.4. Crear y borrar subdirectorios...113
5.5. Modificar la estructura del proyecto ...114
6. Obtener información sobre el proyecto...114
6.1. El comando status...114
6.2. Obtener información detallada de un fichero ...115
6.3. Obtener información de log...116
6.4. Identificar culpables ...119
6.5. Obtener revisiones anteriores...120
6.6. Comparar revisiones...122
7. Ficheros binarios ...123
8. Conflictos ...124
9. Cambiar la URL de la working copy ...126
10. Tagging y branching ...127
10.1. Crear un tag ...127
10.2. Crear una rama...128
10.4. Ramas en revisiones anteriores ...129
10.5. Mezclar ramas...129
10.6. Deshacer la aplicación de una rama ...132
11. Propiedades ...133
11.1. Guardar metadatos en propiedades ...133
11.2. Leer metadatos de las propiedades...134
11.3. Borrar propiedades...135
11.4. Propiedades del sistema ...135
TEMA 9: Subversion desde el punto de vista del administrador 1. Los ficheros de configuración ...141
1.1. El fichero config ...141
1.2. El fichero servers ...142
2. Control de acceso al repositorio ...143
3. Backup del repositorio ...144
4. Hook scripts...144
4.1. Hook scripts disponibles ...144
4.2. Qué puede y qué no puede hacer un hook script ...145
Tema 1
Qué es un gestor
de versiones
Sinopsis:
Este primer tema de naturaleza introductoria pretende fijar conceptos fundamentales, y describir qué tipos de gestores de versiones existen, y cuáles son los problemas que resuelve un gestor de versiones.
Los conceptos aquí descritos son relativamente abstractos, pero su utilidad se materializará en implementaciones concretas durante los temas posteriores.
1. Introducción
Los gestores de versiones (version control system), también llamados
herramientas de gestión de configuraciones software o repositorios, son
herramientas que permiten a los programadores de un proyecto centralizar y coordinar sus trabajos. Los gestores de versiones son especialmente útiles para todo tipo de documentos que sean revisados frecuentemente, como pueda ser el código fuente de un programa, su documentación, cartas, etc...
Normalmente uno de los programadores va a ser el administrador del gestor de versiones, que es el que se encargara de administrar y dar permisos en el gestor de versiones, aunque esta tarea se puede también delegar en una persona especializada en la administración de sistemas informáticos.
Aunque los gestores de versiones están pensados para grupos de trabajo, también son muy útiles para un programador individual, ya que le ayuda a llevar una cuenta histórica de las diferentes versiones de sus ficheros.
En el mundo de UNIX se han utilizado ampliamente cuatro programas para gestión de versiones:
• RCS (Revision Control System). El más antiguo de todos, y que además tiene su código fuente publicado de forma gratuita por la Free Software Foundation. Prácticamente todos los sistemas UNIX lo tienen, y Mac OS X también lo trae preinstalado.
• SCCS (Source Code Control System). Fue introducido por AT&T en el Sistema V de UNIX, y actualmente forma parte del estándar X/Open. Sin embargo Mac OS X no lo trae preinstalado, y nosotros no lo estudiaremos. • CVS (Concurrent Version System). Está basado en RCS, y actualmente es el
gestor de versiones más utilizado por los desarrolladores de software libre en Internet.
• Subversion. Es el resultado de un reingeniería sobre los conceptos de CVS para buscar una solución alternativa a los problemas más comunes con lo que se encuentran los usuarios de CVS. Actualmente, su volumen de usuarios está creciendo rápidamente.
En este documento estudiaremos CVS y Subversion. Aunque usaremos Mac OS X para todos los ejemplos, su interoperatividad hace que las explicaciones puedan ser aplicadas sin problemas en otros entornos UNIX o Windows.
2. Por qué usar un repositorio
Los proyectos de desarrollo de software implican tener a varios desarrolladores trabajando de forma concurrente sobre varios conjuntos de ficheros que con frecuencia se solapan. En consecuencia resulta fundamental poder trazar los cambios hechos por los programadores, de forma que siempre sepamos quién es el responsable de cada cambio. Para ello los gestores de versiones mantienen un sistema de logs. Además los gestores de versiones siempre nos permiten deshacer los cambios para ir a un estado anterior. También es importante que el gestor de
versiones permita mezclar los cambios realizados por los distintos programadores. Los principales argumentos a favor de usar un gestor de versiones son:
• Persistencia. Manteniendo un histórico de revisiones desaparece el problema de perder un código cuando se modifica. Además mantener el código del proyecto centralizado ayuda a realizar copias de seguridad.
• Integración. La integración se realiza implícitamente según los programadores guardan sus contribuciones en el repositorio.
• Contabilidad. Es importante saber quién y cuándo se ha realizado cada cambio en el proyecto. El gestor de versiones permite guardar un histórico de quién ha realizado cada cambio junto con comentarios que los propios programadores guardan indicando el motivo del cambio.
• Branching. Un mismo código se puede utilizar en varios proyectos con sólo hacer pequeñas modificaciones. Los gestores de versiones permiten crear una línea base llamada tronco (trunk) y varias ramas (branches) de un código fuente. Además, el gestor de versiones ayuda a combinar el contenido de las ramas con el tronco. Por ejemplo, un proyecto puede tener un tronco de desarrollo, y una o más ramas para mantenimiento de errores en versiones release antiguas. Esto evita el quebradero de cabeza de tener que mantener sincronizadas varias versiones similares de un código fuente.
• Trabajo distribuido. Los gestores de versiones modernos permiten almacenar el código fuente en un repositorio al que programadores de distintas partes del mundo se conectan a través de Internet.
3. Revisiones, versiones release y variantes
Los gestores de versiones mantienen una copia de todos los ficheros que guardamos en el repositorio a lo largo del ciclo de vida del proyecto, de forma que en cualquier momento podemos "dar marcha atrás" y recuperar una versión que teníamos guardada.
Para ello a las diferentes versiones se las da un número de versión, al que llamaremos revisión, que nos sirve para identificar luego cada versión que hayamos guardado. Aunque muchas veces en la literatura a las revisiones también se las llama versiones, nosotros usaremos el término revisiones para evitar confusiones innecesarias.
Conviene diferenciar entre versiones release de un programa y revisiones. Las
versiones release son versiones que se sacan al público cuando conseguimos tener
el programa en un estado estable, mientras que las revisiones son las que crea el programador cuando al final del día decide guardar su trabajo en el repositorio. Es decir, no tenga miedo de guardar tantas revisiones como quiera.
Por último conviene comentar que el término variante se utiliza para referirnos a varias versiones que coexisten en un mismo instante de tiempo (p.e. para distintos sistemas operativos).
4. Modelos de configuración
Se llama modelo de configuración a la forma de organizar los ficheros que componen un proyecto y a la forma de darles nombre. El modelo de configuración es
el producto cartesiano de dos espacios, el espacio de producto y el espacio de reversiones:
• El espacio de producto son los ficheros que componen el proyecto y las relaciones entre ellos, las cuales pueden ser de dos tipos: Composición, donde un fichero está formado por otros (p.e. las relaciones #include), y dependencia
donde el contenido de un fichero depende de otro fichero.
• El espacio de reversiones muestra la evolución de un fichero a lo largo del tiempo.
Los gestores de versiones están pensados para que almacenen las diferentes revisiones de un fichero de forma eficiente, es decir, sólo almacenan los cambios realizados a cada fichero, no todo el fichero. Se llama delta a la diferencia entre dos revisiones, y los deltas se pueden representar de dos formas distintas:
1. Representación simétrica donde dadas dos revisiones de un fichero r1 y r2, se
almacenan las líneas de texto que están en r1 y no están en r2, y las que están en
r2 y no están en r1 (véase Figura 1.1), es decir, se almacena que líneas de texto
están en cada versión y no en la otra.
2. Representación por cambios. Donde se almacenan los cambios que hay que hacer para pasar de r1 a r2.
5. Versionado extensional e intensional
A la hora de asignar versiones a los ficheros se suelen utilizar en paralelo dos técnicas de versionado:
El más conocido y normal es el versionado extensional (por añadidos), donde se va numerando el contenido de cada fichero según evoluciona, es decir, los cambios que vamos haciendo al fichero en las distintas revisiones.
El otro tipo de versionado es el versionado intensional (que significa dividir en partes), el cual nos permite que de una configuración del software haya varias variantes, y al acceder a la herramienta de gestión de versiones indicamos qué versión es la que queremos coger (p.e. X11 para Linux, Cocoa para Mac OS X, o Win32 para Windows). Este versionado es muy típico gestionarlo con directivas del propio lenguaje como por ejemplo #ifdef de C.
6. Gestores de versiones orientados a ficheros y a
proyectos
En función de la forma en que se asignan revisiones, existen dos tipos de gestores de versiones. Los gestores de versiones orientados a ficheros (p.e. CVS), donde
los números de revisión se asignan para cada fichero de forma individual, y los gestores de versiones orientados a proyectos (p.e. Subversion), donde el número de revisión se asigna a todos los ficheros que componen el proyecto en un momento dado.
La Figura 1.2 muestra un ejemplo de números de revisión asignados a los ficheros de un proyecto en un momento dado, en el caso de CVS. Como vemos, cada fichero tiene un número de revisión distinto, que corresponde con el número de veces que se ha modificado y guardado el fichero en el repositorio. Las revisiones de número más alto de cada fichero corresponden al estado actual del proyecto. En los repositorios orientados a ficheros, como cada fichero crece de forma independiente, es común realizar el tagging, que consiste en etiquetar las revisiones que tienen los ficheros en un momento dado, con el fin de poder recuperar más tarde ese estado. En CVS se puede usar la etiqueta especial HEAD para referirse las últimas revisiones
Tema 2
Elementos del
repositorio
Sinopsis:
En este segundo tema se pretende estudiar a escala conceptual qué elementos forman parte de un repositorio, y cuáles son las operaciones más típicas que se realizan al interactuar con el repositorio.
Es muy recomendable haber leído este tema antes de empezar con los temas específicos de CVS o Subversion.
1. Interacción con el repositorio
Normalmente se llama repositorio a un directorio situado en una máquina (que actúa como servidor) donde se almacenan uno o más proyectos. Por cada proyecto se suele crear un subdirectorio dentro del directorio de repositorio que contiene los ficheros del proyecto. A este directorio se le llama directorio de proyecto. El repositorio puede estar situado en la misma máquina que el programador, pero es más común colocar en repositorio en una máquina distinta y conectarse al repositorio a través de Internet siguiendo el modelo cliente servidor.
Los desarrolladores actúan como clientes, y suelen bajarse una copia de uno o más proyectos a directorios de su máquina de trabajo. A la copia de un proyecto se la llama sandbox (nomenclatura CVS), o working copy (nomenclatura Subversion). Tanto en CVS como en Subversion existe una nomenclatura homogénea para las operaciones que puede realizar el programador con su sandbox respecto al proyecto, que son las siguientes:
1. Import. Es la operación de crear un proyecto en el respositorio a partir de unos ficheros situados en un directorio de la máquina local. Esta operación suele realizarse sólo una vez al principio de un proyecto.
2. Checkout. Es la operación de bajarse un proyecto desde el repositorio a un directorio de la máquina local. Este directorio es el sandbox, y además de los ficheros del proyecto, contiene algunos ficheros con metadatos que sirven al gestor de versiones para conocer informaciones como los logs o las revisiones de un fichero. Esta operación la realiza normalmente sólo una vez cada programador que va a trabajar contra un proyecto almacenado en un repositorio.
3. Export. Es una operación parecida a checkout, pero en vez de estar destinada a programadores que desean crearse una sandbox, está destinada a usuarios que quieren bajarse el código fuente sin ficheros adicionales de metadatos. Es decir, con la operación export, el usuario no obtiene un sandbox, sino sólo un directorio con los ficheros del proyecto listos para ser compilados.
4. Commit. Una vez que el desarrollador modifica uno o más ficheros del proyecto, éste debe subir los cambios al proyecto del repositorio usando la operación de commit. Cuando se hace un commit tanto CVS como Subversion piden introducir un mensaje con una descripción de los cambios realizados.
5. Update. Cuando un programador actualiza el proyecto, los demás desarrolladores pueden bajarse estos cambios utilizando la operación de update.
Los gestores de versiones permiten que un programador modifique su sandbox y, sin necesidad de hacer un commit de los cambios, ejecute la operación de update. En este caso el gestor de versiones es lo suficientemente inteligente como para actualizar en el sandbox sólo las líneas de código cambiadas en el proyecto del repositorio.
Cuando un programador empieza a trabajar con un gestor de versiones suele empezar teniendo bastante miedo a que un update le estropee su código: No tenga ningún miedo a realizar las operaciones commit y update con tanta frecuencia como sea necesario (incluso cuanto más frecuentemente lo haga mejor) y no se preocupe si otros programadores estén también modificando el proyecto del repositorio. Los gestores de versiones son lo suficientemente inteligentes como para no destrozar su código. En la sección 2 veremos que a veces se pueden producir conflictos, pero que su resolución es más sencilla de lo que pueda parecer.
En cualquier caso, y como regla general, se recomienda seguir el siguiente paradigma de interacción con el proyecto del repositorio:
• La operación de update la puede realizar tantas veces como quiera. Por ejemplo la puede realizar todos los días por la mañana antes de ponerse a trabajar en su proyecto, o después de venir de comer. Si su código compilaba antes del update, deberá seguir compilando después del update. Si no es así puede usar las herramientas de logs que proporciona el gestor de versiones para identificar quién ha subido el cambio, y vaya a hablar con él inmediatamente. Si en su grupo de trabajo es frecuente que el código deje de compilar correctamente después de hacer un update, es un síntoma de que una o más personas en su grupo de trabajo no son muy competentes.
• La operación de commit se debe realizar sólo inmediatamente después de hacer un update, y sólo cuando se dispone de un código que compila y ejecuta correctamente. La primera condición garantiza que si otro programador ha actualizado el proyecto del repositorio los cambios del otro programador sean consistentes con los que usted ha realizado. De hecho, los gestores de versiones impiden realizar un commit cuando hay cambios en el proyecto del repositorio que no se han bajado al sandbox con update. La segunda condición garantiza que los demás programadores no se encuentren con un proyecto con código que ni siquiera compila: Un síntoma claro de que el gestor de versiones no se está usando correctamente. Tenga en cuenta que tampoco es conveniente que los periodos de commit se alarguen demasiados: Cuando más se alarguen los periodos de commit, más trabajo se perderá si su máquina falla.
Cuando se hace commit, el gestor de versiones pide un comentario textual que explique los cambios que se han realizado. Es muy común que el programador utilice mensajes poco significativos en estos comentarios, los cuales recuden considerablemente su utilidad. Es muy importante que utilice mensajes informativos que puedan ser luego interpretados por otros programadores. Entre los aspectos que debería incluir este mensaje están: Por qué se ha hecho el cambio, qué funcionalidad se ha añadido, cambiado, y eliminado.
Por último conviene comentar que un error común por parte de los recién llegados a un gestor de versiones es intentar almacenar en el proyecto del repositorio todos los ficheros de su proyecto. En general, en un repositorio sólo deben de almacenarse los ficheros de código fuente (p.e. .c, .h, Makefile) que sirven para generar el
programa, así como los ficheros de documentación (p.e. .doc, .ppt), pero no
deberíamos de almacenar los ficheros que se producen durante la generación del programa, como los .o, los .lib, los .so, los .dll o los .exe. Estos ficheros hacen
crecer mucho el tamaño del repositorio, y no tiene sentido almacenarlos ya que siempre se pueden generar a partir de los ficheros de código fuente. En general, los ficheros de proyecto que crean muchas herramientas de desarrollo tampoco se deben de guardar en el repositorio ya que estos ficheros contienen paths que dependen de la máquina donde se sitúe el sandbox. Los ficheros Makefile o Ant son
una excepción a esta regla siempre que se diseñen de forma que no dependan de rutas absolutas. En el caso de que nuestro proyecto utilice ficheros de ejemplo, como imágenes .jpg, o vídeos .mpg, estos también se pueden almacenar en un directorio
2. Resolución de conflictos
Tanto CVS como Subversion siguen el paradigma de que varios programadores pueden modificar concurrentemente los ficheros del proyecto y después el gestor de versiones se encarga de realizar la mezcla de ficheros. Otros gestores de versiones como Microsoft Visual SourceSafe siguen un paradigma de bloqueos, donde un programador cuando va a codificar un fichero, primero lo bloquea, luego lo modifica, y luego libera el bloqueo.
Los gestores de versiones realizan la mezcla de ficheros de texto línea a línea. Si los cambios están en diferentes líneas, el sistema añade, reemplaza o elimina líneas según proceda. La operación de mezcla será satisfactoria siempre que dos programadores no hayan modificado la misma línea. En caso de que ambos hayan modificado la misma línea se producirá un conflicto (a no ser que hayan modificado exactamente las mismas líneas con el mismo contenido).
En teoría la operación de mezcla podría realizarse tanto en la operación de commit como en la operación de update, pero debido a que los gestores de versiones impiden realizar un commit cuando hay cambios en el proyecto del repositorio (es decir, cuando nuestra revisión del sandbox es más antigua que la del proyecto del repositorio), la operación de mezcla siempre se realiza durante el update. Téngase en cuenta que la operación de mezcla siempre se realiza entre un fichero en el proyecto del repositorio y otro en el sandbox, y el resultado de la mezcla siempre acaba depositándose en el sandbox.
Cuando nos encontramos un conflicto, para resolver el conflicto el gestor de versiones nos muestra dos ficheros: el fichero con los cambios que hemos hecho en el sandbox, y el fichero con los cambios que otro programador hizo en el proyecto del repositorio, y se nos señala la o las líneas conflictivas. En este momento debemos indicar cuál de las dos líneas es la correcta (para lo cual, si tenemos dudas, podemos consultar al otro programador). Una vez indicado cuál es la línea o líneas correctas, obtendremos en el sandbox el fichero actualizado y con los cambios aplicados. En este momento podremos evaluar si todo compila y ejecuta correctamente, y subir los cambios al proyecto del repositorio con la operación de commit.
3. Tagging
Aunque poder obtener la última revisión de los ficheros de un proyecto en el repositorio es útil, también es muy útil poder obtener los ficheros del proyecto en la revisión en la que se encontraban en algún hito pasado (p.e. en la versión 1.0 del proyecto). El tagging permite poner una etiqueta a los ficheros del proyecto tal como se encuentran en un momento dado por si en el futuro quisiéramos obtener está configuración.
En el caso de CVS, como muestra la Figura 1.2, a los ficheros se les asigna números de revisión de forma individual, con lo que el tagging se realiza poniendo la misma etiqueta a cada fichero en su número de revisión actual (p.e. version_1_0).
Por su parte, Subversión implementa el tagging mediante copias ligeras (cheap copies), que consiste en hacer una copia del proyecto (que normalmente está en un directorio llamado trunk) en otro directorio (que normalmente estará metido dentro
del directorio tags).
Subversion no asigna números de revisión de forma individual a cada fichero, sino que por cada revisión que guardamos en el proyecto del repositorio asigna un
número de revisión para todos los ficheros del proyecto. Cuando en Subversion hacemos una copia ligera, no estamos copiando el contenido del fichero (en la revisión actual y en todas las anteriores) sino que sólo estamos apuntando al contenido de la revisión actual en otra ruta. Esto permite que el tagging no consuma muchos recursos. Debido a que las copias ligeras son un puntero a una revisión, sólo pueden hacerse copias ligeras de todos los ficheros del proyecto.
Las estrategias de tagging son muy diversas, pero momentos típicos en los que se hace tagging del proyecto son: Cuando se cumple un hito, cuando se termina una versión release del proyecto, antes de empezar a eliminar una funcionalidad, o antes de empezar a modificar la forma en que está implementada una parte del proyecto.
4. Branching
A la línea principal de desarrollo normalmente se la llama tronco (trunk). El branching consiste en crear una o más líneas de desarrollo distintas a las que se llama ramas (branches).
Existen varias razones para crear una rama: Una razón muy usada es para mantenimiento y corrección de errores de una versión release del producto mientras que el tronco se utiliza para añadir nueva funcionalidad. Otra razón es crear una rama para hacer cambios experimentales o reingeniería de código que en el futuro se podrán añadir o no al tronco de la aplicación.
El gestor de versiones construye la rama y el tronco a partir de las mismas revisiones de código, hasta que llegado un cierto punto, llamado base de la rama, el gestor de versiones almacena los cambios en el tronco y en la rama de forma separada.
En general, deberíamos saber que conviene crear una rama antes de empezar a modificar el código, pero en ocasiones no se decide que conviene crear una rama hasta que el código ha empezado a ser modificado, en este caso podemos hacer un branching retroactivo, pero hacer un branching retroactivo siempre es más complicado que si desde el principio se decide empezar a trabajar en otra rama.
En Subversion la rama debe de incluir todos los ficheros del proyecto. Por contra CVS permite que la rama sólo incluya uno o más ficheros, pero experimentalmente se sabe que siempre que se va a crear una rama, es mejor incluir todos los ficheros del proyecto. En caso contrario es muy común que acabe necesitándose incluir nuevos ficheros en la rama lo cual complica su mantenimiento.
4.1. Números de revisión
Tanto CVS como Subversion asignan un número diferente a cada revisión que almacenamos en el proyecto del repositorio, pero la forma de numerar las revisiones difiere en dos aspectos:
El primer aspecto es que, como adelantamos en el apartado 6 del Tema 1, Subversion es orientado a proyecto, es decir, asigna un mismo número de revisión a todos los ficheros que componen el proyecto en un momento dado, mientras que CVS es orientado a fichero, es decir, tal como muestra la Figura 1.2, a cada fichero se le va asignando números de revisión distintos.
El segundo aspecto a destacar es que Subversion utiliza números consecutivos para cada revisión que se guarda en el proyecto del repositorio, independientemente de si la revisión corresponde al tronco o a una rama. En la Figura 2.1 (a) vemos que los números de revisión en el tronco y en la rama no tienen porque ser consecutivos.
Sin embargo, como muestra la Figura 2.1 (b), CVS asigna números de revisión compuestos por varios números separados por punto. En el caso del tronco el número de revisión suele1 empezar por 1, y después le sigue el número de revisión
del tronco. En el caso de las ramas, las ramas están formadas por tres dígitos2. Los
dos primeros dígitos indican la base de la rama, y el tercero es un número par que indica el número de rama para esa revisión. Por último el cuarto dígito se destina a indicar el número de revisión para una rama.
En cualquier caso la forma en que un gestor de versiones asigna números a las revisiones debe ser vista de forma transparente por parte del usuario, es decir, no se preocupe por qué número de revisión le corresponde a cada revisión que guarde. Si le interesa recordar una revisión por algún motivo, utilice tagging.
Figura 2.1: Branching 1.18 1.19.2.1 1.19 1.20 1.21 1.22 1.19.2.2 1.19.2.3 trunk branch (1.19.2) (b) Branching en CVS trunk branch
(a) Branching en Subversion 34 34 343434 34 353534343434 404034343434 34 34 413434 41 34 34 373434 37 393934343434 34 34 363434 36 383834343434
4.2. Mezclar ramas
Podemos hacer una operación de mezcla consistente en aplicar una rama al
tronco, en cuyo caso aplicamos los cambios que haya sufrido la rama, durante las
revisiones de ésta, a la revisión más reciente del tronco. Cuándo esta mezcla es deseable, depende de la finalidad para la que se creó la rama: Si es una rama de mantenimiento y corrección de errores, seguramente sea deseable hacer esta mezcla. También podría ser interesante aplicar esta mezcla si la rama se creó para desarrollar un código experimental o para hacer reingeniería de código, y el trabajo realizado en la rama fue satisfactorio.
1 Es posible crear varios troncos para un mismo fichero, en cuyo caso el número empezaría por 2, 3,
etc, pero es algo que no se recomienda y no vamos a estudiar aquí. Es decir, el primer dígito indica el número de tronco.
2 Puede crearse una rama de una rama, pero crear ramas anidadas es algo que tampoco se recomienda
También es posible aplicar un tronco a una rama, en cuyo caso aplicamos los cambios que ha sufrido el tronco a la revisión más reciente de la rama.
4.3. Estrategias de branching
En general el tronco representa la principal línea de desarrollo del proyecto, todas las variantes deberían almacenarse en ramas. El principal problema surge a la hora de decidir si el tronco debería mantener código estable o si el mantenimiento y reparación de errores debería realizarse en las ramas. Esto da lugar a las dos principales estrategias de branching que vamos a comentar a continuación: Troncos
estables y troncos inestables.
4.3.1. Troncos estables
La estrategia de troncos estables dice que el tronco debería mantener código que esté siempre listo para release. Las ramas se usan para desarrollo, introducir nueva funcionalidad experimental, para refactorización de código, etc.
La variante más estricta de esta estrategia dice que nada debe de mezclarse en el tronco hasta que no haya pasado por un proceso de aseguramiento de calidad.
En el caso del código fuente abierto, la estrategia de troncos estables es la más popular, ya que cualquier usuario puede bajarse en cualquier momento el código de nuestro proyecto del repositorio, compilarlo, y todo le debería de funcionar.
Otra variante menos estricta dice que el código se envía al tronco una vez acabado (lo que se suele llamar versión beta o release candidate), y en este momento se crea una rama de aseguramiento de calidad, que es la rama de la que saldrá la versión release del producto. A partir del momento en que saquemos la versión release del producto, se crea una rama de mantenimiento, en la que se corrigen posibles errores que surjan más adelante en la versión publicada.
Las ventajas de esta estrategia son que siempre tenemos código estable en el tronco, y que si los desarrollos que hacemos en una rama acaban retrasándose indefinidamente o no terminan de funcionar, no tenemos que deshacer los cambios que haya sufrido el tronco.
La principal desventaja de la estrategia de troncos estables es que el código de la rama puede diferir bastante del código del tronco, con lo que la mezcla con el tronco la debe de realizar una persona que conozca bien los cambios que se han desarrollado en la rama.
4.3.2. Troncos inestables
En esta estrategia, el tronco se utiliza para ir guardando las últimas versiones de código, y cuando se quiere sacar una versión release del producto se crea una rama en la que se realiza el aseguramiento de calidad y mantenimiento de errores.
Esta estrategia es la más usada por consultoras y pequeñas empresas que realizan programas de código cerrado, ya que no existe el problema de que otras personas estén accediendo a nuestro proyecto del repositorio, y tengan que encontrar código estable.
La principal ventaja de esta estrategia es que es más sencilla de seguir ya que a menudo todo el desarrollo (incluido el aseguramiento de calidad y mantenimiento de errores) se realiza en el tronco, con lo que desaparece el problema de tener que mezclar ramas.
El principal inconveniente de esta estrategia es que el tronco suele contener código erróneo que en ocasiones ni si quiera compila. Si decide utilizar la estrategia de troncos inestables, el tagging periódico de revisiones estables puede reducir este inconveniente.
4.4. Tipos de ramas
En cualquier momento podemos aplicar los cambios realizados en una rama al tronco. Una vez aplicados los cambios de la rama al tronco podemos seguir trabajando en la rama, o bien abandonar el desarrollo sobre esa rama. Por desgracia, los gestores de versiones no suelen proporcionar en mecanismo para "cerrar" una rama, sino que lo más que podemos hacer es dejar de trabajar sobre esa rama.
La forma en que usamos las ramas ha dado lugar a dos tipos de ramas que vamos a describir a continuación: Las ramas largas, que son ramas que se mezclan varias veces con el tronco, y las ramas cortas, que son ramas que, una vez mezcladas con el tronco, no se vuelven a usar.
4.4.1. Ramas largas
Una forma de trabajar con ramas largas es la que muestra la Figura 2.2 (a), donde los cambios hechos es la rama se aplican al tronco periódicamente. Esto se hace cuando las ramas están destinadas a aseguramiento de calidad y mantenimiento de código. En este caso, los errores encontrados y corregidos se deben llevar periódicamente al tronco del proyecto. Sin embargo, la nueva funcionalidad añadida al tronco no se suele transportar a las ramas de mantenimiento.
Una segunda forma de trabajar con ramas largas es la que muestra la Figura 2.2 (b) donde los cambios realizados en el tronco se aplican a las ramas. Esto es útil en
situaciones en las que los cambios hechos en las ramas no deben de afectar al tronco, pero los cambios del tronco sí que son útiles para la rama. Por ejemplo, si el tronco representa una librería especializada para procesamiento de imágenes que realizar nuestra empresa, y la rama representa una aplicación que estamos personalizando para un determinado cliente, las mejoras en la librería pueden pasarse a la rama utilizando este modelo.
La tercera forma de trabajar, que muestra la Figura 2.2 (c), es un modelo mixto en el que los cambios se aplican en ambos sentidos. Esto permite que tronco y ramas se sincronicen periódicamente. Este modelo es útil cuando seguimos la estrategia de troncos estables y hay varios desarrolladores trabajando en distintas ramas. Cuando el desarrollo hecho en una rama alcanza un estado estable, su contenido se puede aplicar al tronco, y también se pueden aplicar los cambios del tronco a las ramas para mantener las ramas actualizadas.
4.4.2. Ramas cortas
Las ramas cortas son ramas que sirven para realizar una tarea concreta, y cuyo contenido no se vuelve a usar una vez aplicada la rama al tronco. Cuando se usan ramas cortas, es muy común utilizar ramas cortas en cascada, tal como muestra la Figura 2.3.
Las ramas cortas en cascada simulan una rama larga en la que los cambios se aplican en ambos sentidos: Para evitar tener que aplicar los cambios de la rama al tronco y luego los cambios del tronco a la rama, lo que se hace para mantener las ramas actualizadas es aplicar la rama al tronco y crear otra rama.
Durante la planificación de un proyecto se debe de elegir una estrategia de branching y el tipo de ramas que se van a utilizar en el proyecto. Si un proyecto no tiene una política de branching definida, se suele acabar con un montón de ramas cuya finalidad es difícil de identificar.
5. Ficheros de texto y ficheros binarios
Tanto CVS como Subversion utilizan las líneas de los ficheros de texto para identificar los cambios entre dos revisiones: añadir, borrar, o modificar líneas.
CVS almacena los ficheros de texto con las líneas acabadas siempre al estilo UNIX (en LF). Cuando añadimos al proyecto del repositorio un fichero de texto con líneas al estilo Windows (acabadas en CRLF), o Mac OS Classic (acabadas en CR), CVS modifica el final de línea para almacenar el fichero de texto en el proyecto del repositorio al estilo UNIX, y vuelve a poner el final de línea correspondiente a la plataforma cuando el fichero se lleva del proyecto del repositorio al sandbox. Por su parte Subversion nunca modifica los finales de línea1 de los ficheros.
El hecho de que el gestor de versiones modifique el final de línea dependiendo de la plataforma donde esté el sandbox tiene la ventaja de que las líneas de los ficheros de texto siempre tienen el final de línea preferido, pero el inconveniente de que si el fichero es binario, su contenido se deteriora si se interpretan códigos binarios como códigos de final de línea. Para evitar este problema, en CVS debemos de marcar a los ficheros binarios como fichero binarios: de esta forma CVS no modificará los finales de línea del fichero1.
Otra diferencia importante entre los ficheros de texto y los ficheros binarios, es que los repositorios trabajan en modo merge sólo cuando se trata de ficheros de texto, es decir, almacenan los cambios entre revisiones sólo en el caso de los ficheros de texto. En el caso de los ficheros binarios los repositorios trabajan en
modo copy, en el que siempre que modificamos un fichero binario, se crea otra
copia en el repositorio. Lógicamente el modo copy implica mayor gasto de espacio de almacenamiento, aunque los gestores de versiones usan un algoritmo de compresión basado en diferencias binarias para reducir este consumo.
En el caso de CVS, el modo copy se utiliza cuando el fichero está marcado como binario, si no por defecto se utiliza el modo merge. En el caso de Subversion, no existe un mecanismo para marcar explícitamente a los ficheros como binarios, sino que Subversion identifica automáticamente su tipo MIME. Si su tipo es text/*, las
revisiones del fichero se almacenan en modo merge, en caso contrario las revisiones del fichero se almacenan en modo copy.
6. Herramientas de productividad
Este documento explica el manejo de CVS y Subversion desde un terminal, ya que esta es la forma de poder acceder a toda la funcionalidad que un gestor de versiones ofrece. Sin embargo, una vez que se ha aprendido a usar CVS o Subversion muchos programadores prefieren usar herramientas gráficas que les simplifican el acceso al repositorio o aumentan su productividad. Creemos que es mejor que empiece leyendo este tutorial y una vez sepa manejar estas herramientas desde el terminal empiece a usar la herramienta de productividad que más le guste.
CVS puede ser directamente accedido desde herramientas de desarrollo como Xcode o NetBeans, también existen herramientas para Mac OS X como MacCVS, para Linux como Cervisia, para Windows como o WinCVS, o multiplataforma como SmartCVS. Subversion proporciona herramientas como RapidSVN o SmartSVN que funciona en Mac OS X, Linux y Windows.
7. Hook scripts
Los gestores de versiones suelen permitir que el administrador instale scripts en el repositorio que se ejecutan cuando un usuario va a interactuar con el repositorio. Por ejemplo, cuando se recibe un commit se puede comprobar que el mensaje de log sigua un determinado patrón, o que el código fuente haya sido escrito de acuerdo a las políticas de la empresa. Tanto CVS como subversión permite instalar hook scripts
1 Conviene observar que un error muy típico cometido por los recién llegados a CVS es no marcar los
ficheros binarios como tal, con lo que luego se encuentran con que CVS estropea alguno de sus ficheros binarios.
en el repositorio, y la forma de hacerlo la veremos al final de los temas dedicados a CVS y a Subversion.
8. Repositorios distribuidos
Algunos sistemas de gestión de versiones como Arch, el nuevo gestor de versiones de GNU, o Subversion versión 1.4 o posterior, soportan repositorios distribuidos, los cuales son especialmente útiles para proyectos grandes. Un repositorio distribuido no es más que un repositorio del que existen varios mirror cuyo contenido se sincroniza periódicamente.
En este documento no estudiaremos cómo configurar Subversion para crear repositorios distribuidos, pero el usuario interesado puede buscar esta información al acabar de leer este documento.
Tema 3
Guía rápida de
CVS
Sinopsis:
Este tema pretende resumir los principales aspectos necesarios para el manejo de CVS. Si no tiene mucho tiempo para aprender a utilizar CVS, quizá le sea suficiente con leer este tema. En los siguientes temas se estudiará con más detalle las características y funcionalidades que CVS ofrece.
1. Instalación
CVS es un software de código fuente abierto que ejecuta en la mayoría de las plataformas UNIX existentes: Linux, Mac OS X, FreeBSD, ..., así como en Microsoft Windows.
Básicamente consta de un sólo comando llamado cvs, el cual actúa como cliente
y como servidor:
$ cvs
Usage: cvs [cvs-options] command [command-options-and-arguments] where cvs-options are -q, -n, etc.
(specify --help-options for a list of options) where command is add, admin, etc.
(specify --help-commands for a list of commands or --help-synonyms for a list of command synonyms)
where command-options-and-arguments depend on the specific command (specify -H followed by a command name for command-specific help) Specify --help to receive this message
Además del comando cvs, si va a depositar sus proyectos en un servidor, es muy
recomendable tener instalado el comando ssh.
En Mac OS X tanto ssh como cvs vienen preinstalados, aunque si lo desea puede
bajarse una versión más actualizada del proyecto Fink. En otras plataformas podría no venir instalado alguno de estos comandos, aunque la instalación no debería darle problemas si conoce su sistema operativo.
2. Crear el repositorio
En el apartado 1 del Tema 2 adelantamos que un repositorio no es más que un directorio situado en un servidor, y que un proyecto corresponde a un subdirectorio dentro del directorio del repositorio. Es importante que el directorio donde decidamos crear el repositorio tenga suficiente espacio, ya que los repositorios suelen crecer bastante a lo largo del tiempo.
El repositorio CVS se suele crear en /cvsroot, /var/lib/cvsroot, /home/cvsroot, /usr/local/cvsroot o /usr/local/share/cvsroot. Nosotros
usaremos el directorio /usr/local/share/cvsroot: $ cd /usr/local/share
$ mkdir cvsroot $ ls -l
drwxrwxr-x 2 flh admin 68 Jun 16 10:38 cvsroot drwxr-xr-x 32 root admin 1088 Nov 18 2006 locale drwxr-xr-x 4 root admin 136 Aug 26 2006 man
Los comandos de cvs tienen el formato general:
cvs [global-options] command [command-options] [arguments]
Donde global-options son opciones generales para todos los command de CVS, command-options son opciones particulares para el command que estamos
ejecutando, y arguments son argumentos adicionales que necesita el comando a
ejecutar (p.e. los nombres de los ficheros sobre los que debe actuar el comando). En nuestro caso vamos a empezar ejecutando el comando init, que sirve para
inicializar el directorio de repositorio:
$ cvs -d /usr/local/share/cvsroot init
La opción global -d sirve para indicar la cadena de conexión, que indica dónde
está situado el directorio de repositorio.
Tenga en cuenta que el comando init debe ejecutarse sólo una vez para crear el
directorio de repositorio, no el directorio de proyecto. Un error muy común es ejecutar este comando para crear un nuevo proyecto, lo cual tiene como efecto adverso el que se borran todos los proyectos que existan en el directorio de repositorio.
El comando init crea en el directorio de repositorio un directorio con el nombre CVSROOT donde se almacena toda la información de gestión del repositorio:
$ ls -l /usr/local/share/cvsroot
drwxrwxr-x 50 flh admin 1700 Jun 16 10:50 CVSROOT
En el siguiente apartado veremos que los proyectos se crean como subdirectorios dentro de este directorio de repositorio.
3. Acceso a repositorios remotos
Aunque podemos trabajar con un repositorio local, lo normal es que el repositorio este situado en una máquina distinta. En este apartado vamos a ver cómo establecer la conexión.
Existen varios protocolos de acceso a repositorios remotos, pero el más utilizado es ext, que es el que vamos a explicar en este apartado. Para la conexión ext,
necesitamos tener instalado sshd en el servidor y ssh en el cliente, y disponer de
una cuenta en el servidor, en la cual nos podamos logar usando ssh.
En este ejemplo la cuenta de la que disponemos tiene como usuario flh, como
servidor dymas.ii.uam.es., y como directorio de repositorio /var/lib/cvsroot. $ ssh [email protected]
Password:********** Welcome!
[flh@dymas]~$
En este caso :ext:[email protected]:/var/lib/cvsroot es la cadena de
conexión que debemos pasar a la opción -d para establecer una conexión remota.
Por defecto CVS utiliza rsh para conectarse a un servidor. Por desgracia rsh no
es seguro. Para que CVS utilice ssh debemos de fijar la variable de entorno: $ export CVS_RSH=ssh
Por último comentar que para los accesos remotos por SSH se nos pide un password cada vez que CVS va a interactuar con el repositorio, lo cual resulta molesto y acaba reduciendo la productividad del programador. Por esta razón resulta muy
conveniente activar la identificación por clave pública SSH, lo cual hace que no necesitemos introducir el password en cada conexión.
4. Importar un proyecto
El siguiente paso consiste en crear un proyecto en el repositorio, para lo cual debemos de disponer de los ficheros que inicialmente formarán el proyecto que vamos a crear en el repositorio.
Es importante definir la estructura de subdirectorios antes de importar el proyecto, ya que en CVS renombrar subdirectorios no resulta nada sencillo, de hecho si creamos un subdirectorio y luego lo renombramos, en el proyecto del repositorio tendremos dos subdirectorios, uno con el nombre antiguo y otro con el nombre nuevo. En CVS existe la convención de crear nombres de directorios con tres o cuatro letras minúsculas: src, test, lib, data, doc, etc. El comando que usaremos
para importar el proyecto al repositorio es:
cvs -d conexion_string import project vendor_tag release_tag
Donde project es el nombre del proyecto, es decir, el subdirectorio que se crea
dentro del directorio de repositorio. El vendor_tag es un concepto poco usado que
veremos en el apartado 2.9 del Tema 5. Por desgracia es obligatorio darlo, aunque sólo al importar el proyecto. El release_tag es un tag que se asigna a la primera
revisión del vendor_tag.
#include <stdio.h> int main()
{
printf("Hola mundo controlado en CVS"); return 0;
}
Listado 3.1: Programa hola.c
hola : hola.o
gcc hola.o -o hola hola.o : hola.c
gcc -c hola.c
Listado 3.2: Fichero Makefile
Como ejemplo vamos a crear un proyecto muy sencillo llamado saludos que
constará sólo de dos ficheros: hola.c y Makefile, cuyo contenido se muestran en el
Listado 3.1 y Listado 3.2.
Para ello crearemos en un directorio temporal estos ficheros, nos situaremos el en directorio donde estén los ficheros del proyecto, y ejecutaremos el comando
import1:
1 En este ejemplo y en los siguientes usaremos la cadena de conexión local
/usr/local/share/cvsroot, si desea conectarse al servidor remoto del apartado 3 no tiene más que sustituir la cadena de conexión por :ext:[email protected]:/var/lib/cvsroot.
$ cd tmp $ ls -l
-rw-r--r-- 1 flh admin 67 Jun 16 19:42 Makefile -rw-r--r-- 1 flh admin 86 Jun 16 19:41 hola.c
$ cvs -d /usr/local/share/cvsroot import saludos ninguno ver_inicial N saludos/hola.c
N saludos/Makefile
En este ejemplo hemos usado como vendor_tag la etiqueta ninguno, y como
vendor_tag de cada fichero importado ver_inicial. Como muestra la Figura 3.1,
el comando import ejecuta el editor definido en la variable de entorno EDITOR (joe
en este caso), y nos pide un mensaje de log que asigna a la primera versión del fichero. Las líneas que empiezan por CVS: no serán incluidas en el mensaje de log.
Figura 3.1: Mensaje de log
Una vez importado el proyecto no debemos de trabajar directamente sobre los ficheros importados, sino que, como indica el siguiente apartado, debemos de hacer un checkout de los ficheros del proyecto a un sandbox.
5. Crear el sandbox
CVS almacena los proyectos en un repositorio central, pero los usuarios nunca trabajan directamente sobre el proyecto del repositorio, sino que se bajan una copia llamada sandbox a su disco local. Para crear esa copia se usa el comando checkout,
el cual crea un subdirectorio en el directorio actual con el nombre del proyecto que nos hemos bajado, y deposita en este subdirectorio los ficheros del proyecto. El formato general del comando checkout es:
Por ejemplo, para bajar el proyecto anterior haríamos:
$ cvs -d /usr/local/share/cvsroot checkout saludos cvs checkout: Updating saludos
U saludos/Makefile U saludos/hola.c $ cd saludos $ ls -l
drwxr-xr-x 5 flh admin 170 Jun 17 10:12 CVS -rw-r--r-- 1 flh admin 67 Jun 16 20:01 Makefile -rw-r--r-- 1 flh admin 86 Jun 16 20:01 hola.c
Obsérvese que dentro del subdirectorio saludos se ha creado un subdirectorio CVS,
el cual contiene metadatos útiles para que CVS pueda gestionar el versionado de los ficheros. Además, dentro del subdirectorio CVS se almacena la cadena de conexión,
con lo que a partir de ahora ya no es necesario volver a usar la opción -d, siempre
que ejecutemos el comando cvs dentro del sandbox.
Una forma alternativa de no haber tenido nunca que usar la opción -d es crear la
variable de entorno CVSROOT apuntando al directorio del repositorio: $ export CVSROOT=/usr/local/share/cvsroot
Puede definir esta variable en los ficheros de configuración de su terminal, pero debido a que ya tenemos creado el sandbox, su uso ya no será necesario, y puede llevar a confusión en el futuro si decide trabajar con otro repositorio.
6. Bajar cambios del repositorio
Para bajarnos los cambios que otros programadores hayan hecho en el repositorio usamos el comando update, el cual tiene la forma:
cvs [global-options] update [command-options] [files]
Si no indicamos files se bajan todos los ficheros modificados en el directorio del
repositorio. Por ejemplo si hacemos:
$ cvs update
cvs update: Updating .
Vemos que no se ha modificado ningún fichero en el repositorio. También vemos que ya no hemos necesitado usar la opción -d para indicar dónde está el directorio de
repositorio. Si por el contrarío algún programador hubiera modificado (desde otro sandbox) el fichero Makefile, obtendríamos un mensaje de la forma:
$ cvs update
cvs update: Updating . U Makefile
Cuando este comando se ejecuta reporta una línea de texto por cada fichero modificado, y precede cada línea por un símbolo de acuerdo a la Tabla 3.1. Por ejemplo, la U significa que el fichero ha sido actualizado en nuestro sandbox. La
pocos con lo que no se ha transportado todo el fichero del repositorio al sandbox, sino sólo un patch con los cambios producidos.
Símbolo Descripción
U Fichero más moderno guardado en el repositorio ha sido traído al
sandbox.
P Cambios en el fichero del repositorio han sido transportados al
sandbox. Los cambios eran pocos con lo que no se ha transportado todo el fichero desde el repositorio al sandbox, sino sólo un patch del
fichero.
M El fichero ha sido modificado en el sandbox, pero los cambios no se
han enviado al repositorio. Necesitamos ejecutar commit para que los
cambios se transporten al repositorio.
A El fichero ha sido añadido localmente al sandbox, pero todavía no ha
sido enviado al repositorio. Necesitamos ejecutar commit para que se
transporte al repositorio.
R El fichero ha sido borrado localmente del sandbox, pero el cambio
todavía no se ha transportado al repositorio. Necesitamos ejecutar
commit para que se borre del repositorio.
C El fichero ha sido modificado en las mismas líneas tanto en el
repositorio como en el sandbox, con lo que se ha producido un conflicto.
? El fichero existe en el sandbox, pero no en el repositorio
Tabla 3.1: Símbolos de estado de los ficheros del proyecto
7. Enviar cambios al repositorio
Imaginemos que ahora no queremos limitarnos a bajar los cambios que otros programadores hagan en el repositorio, sino que queremos empezar a editar los ficheros del proyecto, y enviar nuestros cambios al repositorio. Para ello usaremos el comando commit.
Por ejemplo, supongamos que hemos modificado el programa del Listado 3.1 para introducir un mensaje de copyright, tal como muestra el Listado 3.3.
#include <stdio.h> int main()
{
printf("Hola mundo controlado en CVS\n"); printf("Copyright MacProgramadores\n"); return 0;
}
Listado 3.3: Programa hola.c modificado
Para subir los cambios se recomienda ejecutar primero update, ya que de esta
forma podemos identificar si otros programadores han actualizado el proyecto del repositorio. Para asegurar que esta recomendación se cumple, en caso de intentar hacer un commit cuando los ficheros del repositorio hayan sido modificados por otro
programador, CVS falla indicando que los cambios en el repositorio no han sido convenientemente bajados.
$ cvs update
cvs update: Updating . M hola.c
En nuestro caso nadie ha modificado los ficheros del proyecto del repositorio. La M
indica que hemos sido nosotros los que hemos modificado el fichero hola.c en local.
Para subir cambios se usa el comando:
cvs [global-options] commit [command-options] [files]
En caso de no indicar files se suben todos los ficheros modificados en el directorio
actual. En nuestro ejemplo podemos hacer:
$ cvs commit
/usr/local/share/cvsroot/saludos/hola.c,v <-- hola.c new revision: 1.2; previous revision: 1.1
Siempre que hacemos un commit se nos pide introducir un mensaje de log en el
editor por defecto. Procure dar una buena descripción de las razones que motivaron el cambio, la funcionalidad añadida y la funcionalidad eliminada.
8. Añadir ficheros
Para añadir un fichero al proyecto del repositorio no basta con crearlo en el sandbox, sino que debemos seguir el proceso que vamos a describir aquí. Este mecanismo evita que ficheros indeseados (p.e. .o, .exe) se añadan indebidamente al proyecto.
Para añadir un fichero al proyecto del repositorio primero creamos el fichero, y luego ejecutamos sobre el fichero el comando:
cvs [global-options] add [command-options] files
Este comando marca los ficheros dados en files para inclusión en el repositorio.
Obsérvese que es obligatorio indicar el nombre de los ficheros a añadir.
Sin embargo el comando add sólo marca el fichero para inclusión1, pero no lo
sube al repositorio. Para que el nuevo fichero se suba al repositorio debemos ejecutar sobre el fichero el comando commit. Al igual que al hacer un commit de un
cambio, se nos pide un log que describa el motivo de añadir el fichero.
Por ejemplo, para añadir el fichero adios.c al repositorio, deberíamos tenerlo
creado en el sandbox, y haríamos:
$ cvs add adios.c
cvs add: scheduling file `adios.c' for addition
cvs add: use `cvs commit' to add this file permanently
En este momento está marcado en el sandbox para ser añadido. Para transportarlo al repositorio usamos commit:
$ cvs commit
/usr/local/share/cvsroot/saludos/adios.c,v <-- adios.c initial revision: 1.1
Al no indicar nombre de ficheros se suben todos los ficheros modificados en el directorio actual.
9. Borrar ficheros
Para borrar ficheros del repositorio, primero debemos borrarlos del sandbox (p.e. con el comando rm), y luego marcarlos para ser borrados del repositorio con el comando: cvs [global-options] remove [command-options] files
De nuevo el borrado en el repositorio no tiene éxito hasta que se ejecuta el comando
commit sobre el fichero. Por ejemplo para borrar el fichero adios.c del repositorio
haríamos:
$ rm adios.c
$ cvs remove adios.c
cvs remove: scheduling `adios.c' for removal
cvs remove: use `cvs commit' to remove this file permanently $ cvs commit
/usr/local/share/cvsroot/saludos/adios.c,v <-- adios.c new revision: delete; previous revision: 1.1
Un handicap importante de CVS es que, a diferencia de otros gestores de versiones, no permite borrar directorios del repositorio (sólo ficheros). La razón que alegan los creadores de CVS es que es necesario mantener un histórico de los ficheros que alguna vez existieron. En el apartado 2.2 del Tema 4 veremos cómo paliar este problema.
Tema 4
CVS desde el
punto de vista del
usuario
Sinopsis:
El Tema 3 ha proporcionado una descripción rápida de las tareas más comunes que se pueden realizar con CVS. Realmente CVS tiene mucha más funcionalidad. En este tema pretendemos profundizar en estas tareas desde el punto de vista de un usuario que trabaja contra un repositorio CVS ya instalado.
1. El cliente de CVS
En este apartado vamos a empezar profundizando en el funcionamiento general del comando cvs.
1.1. Opciones comunes
En el apartado 2 del Tema 3 adelantamos que el formato general del comando cvs
es:
cvs [global-options] command [command-options] [arguments]
Donde global-options especificaba opciones comunes para todos los comandos de
CVS. Entre estas opciones comunes encontramos:
--help-commands permite obtener un resumen de los comandos existentes en CVS.
Al ejecutar cvs con esta opción obtenemos una salida de la forma: $ cvs --help-commands
CVS commands are:
add Add a new file/directory to the repository admin Administration front end for rcs
annotate Show last revision where each line was modified checkout Checkout sources for editing
commit Check files into the repository diff Show differences between revisions edit Get ready to edit a watched file editors See who is editing a watched file
export Export sources from CVS, similar to checkout history Show repository access history
import Import sources into CVS, using vendor branches init Create a CVS repository if it doesn't exist log Print out history information for files
login Prompt for password for authenticating server logout Removes entry in .cvspass for remote repository ls List files available from CVS
pserver Password server mode
rannotate Show last revision where each line of module was rdiff Create 'patch' format diffs between releases release Indicate that a Module is no longer in use remove Remove an entry from the repository
rlog Print out history information for a module rls List files in a module
rtag Add a symbolic tag to a module server Server mode
status Display status information on checked out files tag Add a symbolic tag to checked out version of unedit Undo an edit command
update Bring work tree in sync with repository version Show current CVS version(s)
watch Set watches
watchers See who is watching a file
--help-options muestra un resumen de las opciones globales que podemos pasar
-H ó --help muestra ayuda sobre el comando que le precede. Por ejemplo: $ cvs --help commit
Usage: cvs commit [-cRlf] [-m msg | -F logfile] [-r rev] files... -c Check for valid edits before committing.
-R Process directories recursively. -l Local directory only (not recursive). -f Force the file to be committed.
-F logfile Read the log message from file. -m msg Log message.
-r rev Commit to this branch or trunk revision.
-q y -Q permiten silenciar los mensajes de CVS. Con -q sólo produce mensajes de
error, con -Q no produce nunca ningún mensaje.
-n evita que se cambien los ficheros del sandbox o del repositorio. Esta opción es útil
para simular la ejecución de un comando pero sin que se ejecute realmente.
-v muestra la versión de CVS e información de copyright.
-t hace una traza de la ejecución del programa, indicando cada operación del
programa CVS que se ejecuta.
-e indica el editor que debe usarse para recoger los mensajes de log.
Para decidir el editor a usar, CVS asigna más prioridad al editor dado por la opción global -e, si esta opción no se proporciona, usa el editor dado en las siguientes
variables de entorno (de mayor a menor prioridad): CVSEDITOR, EDITOR, VISUAL.
Para evitar que comandos como add, commit o import lancen el editor podemos
usar la opción de comando -m e indicar el mensaje como argumento. Por ejemplo: $ cvs commit -m "Modificado el mensaje de saludo" hola.c
1.2. Permisos de fichero
Los ficheros del sandbox pierden sus permisos de fichero cuando son almacenados en el repositorio. En el caso de los sistemas UNIX, cuando los ficheros se vuelven a llevar a otro sandbox adquieren el usuario y grupo del programador que crea el sandbox. En concreto, en el sandbox los ficheros adquieren los permisos de fichero que tengamos configurados en umask.
2. Mantener actualizado el repositorio
En este apartado vamos a ver cómo podemos mantener actualizado el sandbox con el proyecto de repositorio.