Gestión de versiones con CVS y Subversion
Transcripción
Gestión de versiones con CVS y Subversion
Gestión de versiones con CVS y Subversion Gestión de versiones con CVS y Subversion macprogramadores.org 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 www.macprogramadores.org, 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: [email protected] Pág 2 Gestión de versiones con CVS y Subversion macprogramadores.org Tabla de Contenido TEMA 1: Qué es un gestor de versiones 1. 2. 3. 4. 5. 6. Introducción ................................................................................................ 8 Por qué usar un repositorio ........................................................................... 8 Revisiones, versiones release y variantes........................................................ 9 Modelos de configuración.............................................................................. 9 Versionado extensional e intensional ............................................................ 10 Gestores de versiones orientados a ficheros y a proyectos ............................. 10 TEMA 2: Elementos del repositorio 1. 2. 3. 4. Interacción con el repositorio ...................................................................... 13 Resolución de conflictos .............................................................................. 15 Tagging..................................................................................................... 15 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. 2. 3. 4. 5. 6. 7. 8. 9. Instalación................................................................................................. 24 Crear el repositorio ..................................................................................... 24 Acceso a repositorios remotos ..................................................................... 25 Importar un proyecto ................................................................................. 26 Crear el sandbox ........................................................................................ 27 Bajar cambios del repositorio....................................................................... 28 Enviar cambios al repositorio ....................................................................... 29 Añadir ficheros ........................................................................................... 30 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.3. Comparar revisiones de un fichero......................................................... 39 Pág 3 Gestión de versiones con CVS y Subversion macprogramadores.org 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.4. Los ficheros readers y writers................................................................ 85 Pág 4 Gestión de versiones con CVS y Subversion macprogramadores.org 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. 2. 3. Características de Subversion ...................................................................... 94 Instalación................................................................................................. 95 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. 2. 3. 4. 5. El cliente de Subversion .............................................................................105 Exportar un proyecto .................................................................................106 Layout del repositorio ................................................................................106 Mantener actualizada la working copy .........................................................108 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.3. Ramas retroactivas............................................................................128 Pág 5 Gestión de versiones con CVS y Subversion macprogramadores.org 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 4.3. Comprobar la correcta indentación de los ficheros .................................146 Pág 6 Gestión de versiones con CVS y Subversion macprogramadores.org 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. Pág 7 Gestión de versiones con CVS y Subversion 1. macprogramadores.org 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 Pág 8 Gestión de versiones con CVS y Subversion macprogramadores.org versiones permita mezclar los cambios realizados por los distintos programadores. Los principales argumentos a favor de usar un gestor de versiones son: • • • • • 3. 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. 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 Pág 9 Gestión de versiones con CVS y Subversion macprogramadores.org 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 Pág 10 Gestión de versiones con CVS y Subversion macprogramadores.org 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 de todos los ficheros del proyecto. Pág 11 Gestión de versiones con CVS y Subversion macprogramadores.org 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. Pág 12 Gestión de versiones con CVS y Subversion 1. macprogramadores.org 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. Pág 13 Gestión de versiones con CVS y Subversion macprogramadores.org 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 destinada a iconos o casos de prueba. Pág 14 Gestión de versiones con CVS y Subversion 2. macprogramadores.org 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 Pág 15 Gestión de versiones con CVS y Subversion macprogramadores.org 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. Pág 16 Gestión de versiones con CVS y Subversion macprogramadores.org 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. branch trunk 34 34 34 34 34 35 34 34 36 34 34 38 34 34 41 34 34 37 34 34 39 34 34 40 (a) Branching en Subversion branch (1.19.2) trunk 1.18 1.19 1.19.2.1 1.19.2.2 1.19.2.3 1.20 1.21 1.22 (b) Branching en CVS Figura 2.1: Branching 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 por la complejidad que introduce. Pág 17 Gestión de versiones con CVS y Subversion macprogramadores.org 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. Pág 18 Gestión de versiones con CVS y Subversion macprogramadores.org 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 Pág 19 Gestión de versiones con CVS y Subversion macprogramadores.org 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. 1 A no ser que usemos la propiedad svn:eol-style Pág 20 Gestión de versiones con CVS y Subversion macprogramadores.org 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. Pág 21 Gestión de versiones con CVS y Subversion macprogramadores.org 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. Pág 22 Gestión de versiones con CVS y Subversion macprogramadores.org 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. Pág 23 Gestión de versiones con CVS y Subversion 1. macprogramadores.org 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 drwxr-xr-x 32 root admin drwxr-xr-x 4 root admin 68 Jun 16 10:38 cvsroot 1088 Nov 18 2006 locale 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 Pág 24 Gestión de versiones con CVS y Subversion macprogramadores.org 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 Pág 25 Gestión de versiones con CVS y Subversion macprogramadores.org 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 1 import : 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. Pág 26 Gestión de versiones con CVS y Subversion macprogramadores.org $ 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: cvs [global-options] checkout [command-options] project Pág 27 Gestión de versiones con CVS y Subversion macprogramadores.org 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 opción P es parecida, sólo que indica que los cambios en el repositorio han sido Pág 28 Gestión de versiones con CVS y Subversion macprogramadores.org 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 U P M A R C ? Descripción Fichero más moderno guardado en el repositorio ha sido traído al sandbox. 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. 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. 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. 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. 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.org\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 Pág 29 Gestión de versiones con CVS y Subversion macprogramadores.org 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 new revision: 1.2; previous revision: 1.1 <-- hola.c 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: 1 De momento sólo vamos a ver cómo se añaden ficheros de texto. En el apartado 6.1 del Tema 4 veremos cómo se hace para añadir ficheros binarios. Pág 30 Gestión de versiones con CVS y Subversion macprogramadores.org $ cvs commit /usr/local/share/cvsroot/saludos/adios.c,v initial revision: 1.1 <-- adios.c 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. Pág 31 Gestión de versiones con CVS y Subversion macprogramadores.org 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. Pág 32 Gestión de versiones con CVS y Subversion 1. macprogramadores.org 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 a CVS. Pág 33 Gestión de versiones con CVS y Subversion macprogramadores.org -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. 2.1. Subir ficheros al repositorio En el apartado 7 del Tema 3 vimos que el formato general del comando commit es: Pág 34 Gestión de versiones con CVS y Subversion macprogramadores.org cvs [global-options] commit [command-options] [files] Cuando ejecutamos el comando commit sin indicar files, se hace un commit de todos los ficheros modificados en el directorio actual de forma recursiva, si queremos que el commit no sea recursivo podemos usar la opción de comando -l. En concreto, las opciones de comando más importantes de commit son: -l realiza un commit no recursivo de los ficheros del directorio actual. -R realiza un commit recursivo (valor por defecto) de los ficheros del directorio actual. -m permite indicar el mensaje de log. De esta forma no se muestra el editor para introducirlo. -F permite indicar un fichero donde está el mensaje de log a adjuntar. 2.2. Bajar ficheros del repositorio En el apartado 6 del Tema 3 vimos que el formato general del comando update es: cvs [global-options] update [command-options] [files] Cuando CVS actualiza los ficheros del sandbox, siempre intenta preservar los cambios que tengamos hechos en estos ficheros. Para ello normalmente hace una mezcla entre los ficheros del repositorio y del sandbox. En el apartado 9 del Tema 3 vimos que CVS no permite borrar subdirectorios. Lo que CVS sí que permite es dejarlos vacíos (borrar sus ficheros). Para paliar este problema el comando update por defecto no descarga subdirectorios del proyecto del repositorio que no estén en el sandbox. De esta forma no se bajan al sandbox los subdirectorios vacíos (vestigios de subdirectorios que alguna vez contuvieron ficheros). Si queremos que update baje al sandbox los subdirectorios del proyecto del repositorio, debemos añadir la opción de comando -d. La opción -d tiene el efecto lateral de que baja tanto subdirectorios con ficheros como subdirectorios vacíos. Si no queremos que se bajen los subdirectorios vacíos debemos de usar la opción -P (prune) cada vez que usemos la opción -d. De hecho, lo normal es, o no usar ninguna de estas opciones, o bien usar ambas opciones a la vez. Hay que tener en cuenta que checkout sí que descarga todos los subdirectorios del proyecto del repositorio al sandbox, con lo que siempre que usemos el comando checkout conviene acompañarlo con la opción de comando -P. En caso de no usar la opción -P al ejecutar el comando checkout corremos el riesgo de descargarnos subdirectorios vacíos que ya no se están usando. En ocasiones descubrimos que los cambios que hemos hecho a un fichero del sandbox no son útiles y los queremos descartar. Para ello tenemos dos formas: La primera es pasar la opción de comando -C a update, la cual hace que se descargue una nueva copia del fichero del proyecto del repositorio al sandbox y se pierden los cambios hechos en el sandbox. La otra opción es simplemente borrar el fichero y volver a ejecutar update. Pág 35 Gestión de versiones con CVS y Subversion 3. macprogramadores.org Obtener información sobre el proyecto Existen varios comandos para obtener información sobre el estado del proyecto que vamos a estudiar en los siguientes apartados. 3.1. Estado de los ficheros El primero que vamos a comentar es el comando status, que tiene la forma: cvs [global-options] status [command-options] [files] Aunque el parámetro files es opcional, es muy recomendable usarlo para indicar sólo un fichero, ya que si no indicamos este parámetro el comando status nos muestra información sobre todos los ficheros del directorio y subdirectorios, lo cual da lugar a una cantidad de texto abrumadora. Por ejemplo, podemos obtener información sobre el estado del fichero hola.c ejecutando el comando: $ cvs status hola.c ============================================================== File: hola.c Status: Up-to-date Working revision: 1.2 2007-06-17 10:59:25 +0200 Repository revision: 1.2 /usr/local/share/cvsroot/saludos/hola.c,v Commit Identifier: cxq9FNQqo2piefms Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) El campo Status: indica el estado del fichero, y se nos puede dar uno de estos valores: Up-to-date El fichero en el sandbox está sincronizado con el proyecto del repositorio. Needs Checkout El fichero existe en el proyecto del repositorio pero no en el sandbox. Needs Patch En el repositorio hay una versión más moderna del fichero (que ha puesto otro programador) y el fichero del sandbox necesita actualizarse. Locally Modified El fichero del sandbox ha sido modificado, pero no se ha hecho un commit de los cambios. Locally Added El fichero del sandbox ha sido añadido al proyecto (con el comando add), pero no se ha hecho commit del cambio. Locally Removed El fichero del sandbox ha sido borrado localmente (con el comando remove) pero no se ha hecho commit del cambio. File had conflicts on merge hay un conflicto entre el fichero del proyecto del repositorio y el del sandbox. Esta situación ocurre cuando otro programador modificó las mismas líneas que nosotros en el repositorio, y él envió primero los cambios al repositorio. Unresolved Conflict La copia del repositorio y del sandbox no se pueden mezclar. Esta situación ocurre cuando en el sandbox hay un fichero con el mismo Pág 36 Gestión de versiones con CVS y Subversion macprogramadores.org nombre que en el proyecto del repositorio, pero que no procede del repositorio. Unknown El fichero es desconocido para CVS. Esta situación se produce cuando ejecutamos status sobre un fichero del sandbox que no ha sido añadido al proyecto. El comando update muestra el símbolo ? cuando encuentra este tipo de ficheros. Los campos Working revision: y Repository revision: muestran respectivamente la reversión del fichero que hay en el sandbox y en el repositorio. En el caso de que los fichero estén actualizados estos números de reversión deberían coincidir. Es posible comprobar el estado de un fichero borrado. Por ejemplo, si preguntamos por el fichero adios.c, que borramos en el apartado 9 del Tema 3, obtenemos: $ ls -l total 16 drwxr-xr-x 5 flh admin 170 Jun 23 09:41 CVS -rw-r--r-1 flh admin 67 Jun 17 10:41 Makefile -rw-r--r-1 flh admin 133 Jun 23 09:41 hola.c $ cvs status adios.c ============================================================== File: no file adios.c Status: Up-to-date Working revision: No entry for adios.c Repository revision: 1.2 /usr/local/share/cvsroot/saludos/Attic/adios.c,v Commit Identifier: Vo21cNCwF3089hms El campo Repository revision:, además de la versión del fichero en el repositorio, muestra el path del repositorio donde está almacenado el fichero. En caso de que el fichero haya sido borrado (como pasa con el fichero adios.c), éste se guarda en el repositorio en el subdirectorio Attic, lo cual permitirá recuperarlo si en el futuro hiciese falta recuperarlo1. En ocasiones lo único que nos interesa es conocer el estado de los ficheros respecto al repositorio. En este caso un buen truco es usar el comando update con la opción global -n que, como adelantamos en el apartado 1.1, hace que CVS no cambie nada en el sandbox ni en el proyecto del repositorio, pero sí produzca un informe de los ficheros (presuntamente) modificados. Por ejemplo: $ cvs -n update cvs update: Updating . U hola.c ? README.txt En este caso CVS nos está informando de que el fichero hola.c ha sido actualizado en el sandbox (U ), pero la opción -n evita que los cambios se envíen al sandbox. También nos informa de que el fichero README.txt existe en el sandbox, pero no ha sido añadido al proyecto del repositorio (?). Por último comentar que podemos obtener un listado de los ficheros disponibles en el proyecto del repositorio con el comando de CVS ls (que no debe ser confundido con el comando ls de Bash): 1 Esta forma de guardar ficheros borrados es la que ha dificultado el que CVS pueda borrar directorios. Pág 37 Gestión de versiones con CVS y Subversion $ ls -l total 16 drwxr-xr-x -rw-r--r--rw-r--r--rw-r--r-$ cvs ls Makefile hola.c 5 1 1 1 flh flh flh flh admin admin admin admin 170 67 0 133 macprogramadores.org Jun Jun Jun Jun 23 17 23 23 10:14 10:41 10:16 10:14 CVS Makefile README.txt hola.c Obsérvese que el fichero Makefile no aparece en el listado dado por el comando update porque pertenece al sandbox y no ha sido modificado. El directorio CVS contiene metadatos y no aparece nunca en la salida del comando update. 3.2. Evolución histórica de los ficheros En ocasiones nos conviene conocer la evolución histórica que han sufrido uno o más ficheros a lo largo de la vida del proyecto. Para ello podemos usar el comando log, el cual tiene la forma general: cvs [global-options] log [command-options] [files] Al igual que con el comando status, es muy conveniente indicar un fichero, ya que de lo contrario se muestra la evolución histórica de todos los ficheros del directorio actual y subdirectorios. Por ejemplo, si preguntamos por la evolución histórica del fichero hola.c, obtenemos: $ cvs log hola.c RCS file: /usr/local/share/cvsroot/saludos/hola.c,v Working file: hola.c head: 1.2 branch: locks: strict access list: symbolic names: ver_inicial: 1.1.1.1 ninguno: 1.1.1 keyword substitution: kv total revisions: 3; selected revisions: 3 description: ---------------------------revision 1.2 date: 2007-06-17 11:00:03 +0200; author: flh; state: Exp; lines: +2 -1; commitid: cxq9FNQqo2piefms; Anadido copyright ---------------------------revision 1.1 date: 2007-06-16 20:01:34 +0200; author: flh; state: Exp; commitid: DG9Gf60O4Cpcfams; branches: 1.1.1; Initial revision ---------------------------revision 1.1.1.1 date: 2007-06-16 20:01:34 +0200; author: flh; state: Exp; lines: +0 -0; commitid: DG9Gf60O4Cpcfams; Esto es un proyecto de ejemplo ============================================================== Pág 38 Gestión de versiones con CVS y Subversion macprogramadores.org El hecho de que la vida del fichero empiece en la versión 1.1.1.1 (que corresponde al tag ver_inicial) indica que el fichero existía cuando se creo el proyecto del repositorio (con el comando import ). También podemos preguntar por la evolución histórica del fichero borrado adios.c, pero como esté se creo después de crear el proyecto del repositorio, no existirá la versión 1.1.1.1. $ cvs log adios.c RCS file: /usr/local/share/cvsroot/saludos/Attic/adios.c,v Working file: adios.c head: 1.2 branch: locks: strict access list: symbolic names: keyword substitution: kv total revisions: 2; selected revisions: 2 description: ---------------------------revision 1.2 date: 2007-06-17 16:46:12 +0200; author: flh; state: dead; +0 -0; commitid: Vo21cNCwF3089hms; Ya no vale ---------------------------revision 1.1 date: 2007-06-17 16:28:19 +0200; author: flh; state: Exp; commitid: QPhVudiin4DY2hms; Otro fichero de ejemplo lines: El campo state: con el valor dead indica que el fichero ha sido borrado. El comando log tiende a producir mucha información. Las opciones del comando permiten limitar esta información. Por ejemplo, la opción -R hace que sólo se muestre el fichero del repositorio donde está almacenado: $ cvs log -R hola.c /usr/local/share/cvsroot/saludos/hola.c,v La opción -h hace que sólo se muestren la última versión, la opción -s states hace que sólo se muestre información sobre ficheros en un determinado estado. O la opción -r revisions hace que sólo se muestren las revisiones indicadas. 3.3. Comparar revisiones de un fichero Podemos usar el comando diff para comparar dos revisiones de un fichero y mostrar las diferencias. Su sintaxis general es: cvs [global-options] diff [command-options] [files] El comando normalmente recibe la opción -r dos veces, para indicar las dos revisiones sobre las que se va a realizar la comparación. Por ejemplo, para ver las diferencias entre la revisión 1.1 y 1.2 de hola.c hacemos: $ cvs diff -r 1.1 -r 1.2 hola.c Index: hola.c ============================================================== Pág 39 Gestión de versiones con CVS y Subversion macprogramadores.org RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.1 retrieving revision 1.2 diff -r1.1 -r1.2 5c5,6 < printf("Hola mundo controlado en CVS"); --> printf("Hola mundo controlado en CVS\n"); > printf("Copyright macprogramadores.org\n"); Lo cual indica que el la revisión 1.2 se ha añadido un \n al final del primer mensaje y se ha añadido el mensaje de copyright. El orden en que se dan los números de revisiones es importante. Si hacemos: $ cvs diff -r 1.2 -r 1.1 hola.c Index: hola.c ============================================================== RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.2 retrieving revision 1.1 diff -r1.2 -r1.1 5,6c5 < printf("Hola mundo controlado en CVS\n"); < printf("Copyright macprogramadores.org\n"); --> printf("Hola mundo controlado en CVS"); Lo que estamos preguntando es por los cambios para ir de la revisión 1.2 a la revisión 1.1. En caso de proporcionarse sólo un número de revisión, se compara el número de revisión dado con el contenido del sandbox: $ cvs diff -r 1.1 hola.c Index: hola.c ============================================================== RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.1 diff -r1.1 hola.c 5c5,7 < printf("Hola mundo controlado en CVS"); --> printf("Hola mundo controlado en CVS\n"); > printf("Esta linea esta en el sandbox\n"); > printf("Copyright macprogramadores.org\n"); Obsérvese que a diferencia del caso anterior no se ha bajado la revisión 1.2 del repositorio, sino que se ha usado el fichero en el sandbox. El comando diff de CVS (al igual que el comando diff de GNU en el cual se basa), además del formato que hemos visto para mostrar las diferencias, tiene otros formatos para mostrar las diferencias dependiendo de la opción que le pasemos: -n Usa el formato de RCS para mostrar las diferencias encontradas -c Muestra diferencias contextuales. En esta forma no se indican las líneas que difieren, sino las líneas que rodean a los cambios. -u Usa el formato unificado para mostrar las diferencias. En este formato se usan los símbolos + y - para indicar líneas añadidas y eliminadas, el símbolo ! para indicar las líneas modificadas. -y Muestra las diferencias entre los ficheros en dos columnas. Pág 40 Gestión de versiones con CVS y Subversion 3.4. macprogramadores.org Procedencia de las líneas Una forma de saber de donde procede cada línea de un fichero es el comando annotate. Este comando muestra junto a cada línea, la versión en que se modificó la línea por última ver, y quién realizó el cambio: $ cvs annotate hola.c Annotations for hola.c *************** 1.1 (flh 16/6/07): #include <stdio.h> 1.1 (flh 16/6/07): 1.1 (flh 16/6/07): int main() 1.1 (flh 16/6/07): { 1.2 (ana 17/6/07): printf("Hola mundo controlado en CVS\n"); 1.2 (ana 17/6/07): printf("Copyright macprogramadores.org\n") 1.1 (flh 16/6/07): return 0; 1.1 (flh 16/6/07): } En este ejemplo vemos que el fichero hola.c fue creado por el usuario flh y modificado por el usuario ana. 4. Obtener revisiones anteriores Podemos obtener revisiones anteriores de uno o más ficheros almacenados en el repositorio por número de revisión, o por fecha. Para obtener una revisión, primero podemos usar los comandos log y diff para consultar la evolución de los ficheros del proyecto, y luego usaremos el comando update junto con la opción -r para indicar la versión a obtener. Por ejemplo, para obtener la versión 1.1 del fichero hola.c hacemos: $ cvs update -r 1.1 hola.c U hola.c Siempre que obtenemos en el sandbox una revisión anterior de un fichero, este fichero no debe de ser modificado directamente. De hecho, si lo modificamos e intentamos hacer un commit del fichero obtenemos un mensaje de la forma: $ cvs commit hola.c cvs commit: sticky tag `1.1' for file `hola.c' is not a branch cvs [commit aborted]: correct above errors first! Esto se debe a que cuando nos bajamos una revisión anterior de un fichero, sobre el fichero se pone una marca llamada sticky tag (en caso de habernos bajado una revisión dando el número de revisión), o sticky date (en caso de habernos bajado una revisión dando la fecha). Para observar esta marca podemos usar el comando status: $ cvs status hola.c ============================================================== File: hola.c Status: Up-to-date Working revision: 1.1 2007-06-24 19:02:47 +0200 Repository revision: 1.1 Pág 41 Gestión de versiones con CVS y Subversion macprogramadores.org /usr/local/share/cvsroot/saludos/hola.c,v Commit Identifier: DG9Gf60O4Cpcfams Sticky Tag: 1.1 Sticky Date: (none) Sticky Options: (none) Donde se nos indica que sobre el fichero se ha puesto un sticky tag para la versión 1.1. Para eliminar un sticky tag o sticky date debemos ejecutar el comando update con la opción -A : $ cvs update -A hola.c U hola.c $ cvs status hola.c ============================================================== File: hola.c Status: Up-to-date Working revision: 1.2 2007-06-24 19:07:57 +0200 Repository revision: 1.2 /usr/local/share/cvsroot/saludos/hola.c,v Commit Identifier: cxq9FNQqo2piefms Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) 4.1. Recuperar código anterior En este apartado vamos a ver cómo podemos recuperar código anterior que un programador haya subido al repositorio. Imaginemos que creamos una nueva versión del fichero hola.c como muestra el Listado 4.1. Es decir, hemos cambiado el mensaje de copyright por otro mensaje. int main() { printf("Hola mundo controlado en CVS\n"); printf("Este fichero esta mas completo que antes\n"); return 0; } Listado 4.1: El fichero hola.c con más texto Y hacemos un commit de los cambios: $ cvs commit hola.c /usr/local/share/cvsroot/saludos/hola.c,v new revision: 1.3; previous revision: 1.2 <-- hola.c Imaginemos que ahora queremos recuperar el mensaje de copyright que teníamos en la revisión 1.2 (véase Listado 3.3). Para ello podemos recuperar la revisión 1.2, pero como una vez recuperada se activa el sticky tag, no podemos subir los cambios que hagamos sobre el fichero. Una primera forma de resolver este problema es bajarnos la revisión 1.2 del fichero hola.c, hacer una copia en otro fichero, y recuperar la última versión del fichero hola.c: $ cvs update -r 1.2 hola.c U hola.c $ cp hola.c temp.hola.c Pág 42 Gestión de versiones con CVS y Subversion macprogramadores.org $ cvs update -A hola.c U hola.c Ahora podemos copiar el mensaje de copyright desde temp.hola.c a la posición que deseemos de hola.c. Una mejor opción es usar la opción -p del comando update para que la versión antigua se guarde en otro fichero temporal. Como la opción -p por defecto se escribe el fichero por la salida estándar. Podemos redirigir esta salida a un fichero de la forma: $ cvs update -r 1.2 -p > temp.hola.c La ventaja de esta segunda aproximación es que no activa el sticky tag sobre el fichero. 4.2. Deshacer cambios En este apartado vamos a ver cómo podemos deshacer cambios que, por error, un programador hubiera subido al repositorio. Imaginemos que tenemos un nuevo programador (que nos ha obligado a aceptar el departamento de RRHH), que modifica el Listado 4.1 para destrozarlo tal como muestra el Listado 4.2. #include <stdio.h> int main() { printf("Jola mundo controlao en CVS\n"); printf("Ete ficheo eta ma completao que anteriormente\n"); return 0; } Listado 4.2: Fichero hola.c destrozado Y el nuevo programador sube el fichero al repositorio con número de revisión 1.4: $ cvs commit hola.c /usr/local/share/cvsroot/saludos/hola.c,v new revision: 1.4; previous revision: 1.3 <-- hola.c Claramente tenemos que deshacer estos cambios y volver al estado que estábamos en la revisión 1.3. Una forma de volver al estado de la revisión 1.3 es preguntar a CVS por los cambios que hay para ir de la revisión 1.4 a la revisión 1.3 (el orden en que se dan los números de revisión es importante). Obsérvese que estamos preguntando por cambios "en marcha atrás" (backward). $ cvs diff -r 1.4 -r 1.3 hola.c Index: hola.c ============================================================== RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.4 retrieving revision 1.3 diff -r1.4 -r1.3 5,6c5,6 < printf("Jola mundo controlao en CVS\n"); Pág 43 Gestión de versiones con CVS y Subversion macprogramadores.org < printf("Ete ficheo eta ma completao que anteriormente\n"); --> printf("Hola mundo controlado en CVS\n"); > printf("Este fichero esta mas completo que antes\n"); CVS lo que nos dice es que debemos borrar las dos líneas con errores ortográficos que introdujo el nuevo programador, y escribir las líneas que teníamos en la revisión 1.3. Para aplicar estos cambios en el fichero hola.c del sandbox podemos ejecutar el comando (de nuevo, el orden en que se dan los números de revisión es importante): $ cvs update -j 1.4 -j 1.3 hola.c RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.4 retrieving revision 1.3 Merging differences between 1.4 and 1.3 into hola.c Ahora podemos subir el fichero arreglado al repositorio: $ cvs commit /usr/local/share/cvsroot/saludos/hola.c,v new revision: 1.5; previous revision: 1.4 <-- hola.c Obsérvese que la política de CVS (y de casi todos los gestores de versiones) es guardar copias de todas las revisiones: La inicialmente buena (1.3), la que creó el nuevo programador (1.4), y la que arreglamos nosotros (1.5), que es idéntica a la 1.3. Hay programadores que prefieren poder borrar una revisión que han subido al repositorio. Aunque CVS no recomienda borrar revisiones del proyecto del repositorio, sí que proporciona una forma de hacerlo, que es con el comando admin y la opción de comando -o. En concreto podríamos haber borrado la revisión 1.4 del proyecto del repositorio con el comando: $ cvs admin -o 1.4 hola.c RCS file: /usr/local/share/cvsroot/saludos/hola.c,v deleting revision 1.4 done Este comando borra la revisión 1.4 del repositorio, pero no actualiza el sandbox. Para actualizar el sandbox (y bajarnos la revisión 1.3) podemos ejecutar a continuación: $ cvs update hola.c U hola.c 4.3. Recuperar ficheros eliminados En este apartado vamos a ver cómo podemos recuperar ficheros que hayamos eliminado del proyecto del repositorio con el comando remove. Recuérdese que en el apartado 9 del Tema 3 borramos un fichero adios.c que tiene el siguiente histórico: $ cvs log adios.c RCS file: /usr/local/share/cvsroot/saludos/Attic/adios.c,v Working file: adios.c head: 1.2 branch: locks: strict Pág 44 Gestión de versiones con CVS y Subversion macprogramadores.org access list: symbolic names: keyword substitution: kv total revisions: 2; selected revisions: 2 description: ---------------------------revision 1.2 date: 2007-06-17 16:46:12 +0200; author: flh; state: dead; lines: +0 -0; commitid: Vo21cNCwF3089hms; Ya no vale ---------------------------revision 1.1 date: 2007-06-17 16:28:19 +0200; author: flh; state: Exp; commitid: QPhVudiin4DY2hms; Otro fichero de ejemplo ============================================================== Es decir, la revisión 1.2 lo que realmente hace es eliminar el fichero. Podemos recuperar ficheros eliminados con el comando update y el número de revisión en que se encontraba el fichero antes de ser borrado. En el caso del fichero adios.c deberíamos recuperar la revisión 1.1, que es la última revisión antes de ser borrado: $ cvs update -r 1.1 adios.c U adios.c $ cat adios.c #include <stdio.h> int main() { printf("Adios mundo controlado en CVS\n"); printf("Copyright macprogramadores.org\n"); return 0; } 4.4. Obtener revisiones por fecha Hasta ahora hemos visto cómo obtener revisiones anteriores dando el número de revisión. Existe otra forma de indicar una revisión a obtener que es una fecha (y opcionalmente también hora). La opción -D sirve para indicar que queremos obtener un fichero tal cómo se encontraba en una determinada fecha. CVS acepta varios formatos de fecha, incluidos ISO 8601, RFC 822 y RFC 1123, así como algunos términos en inglés como yesterday. Si quiere conocer estos formatos quizá lo más cómodo sea ejecutar el comando log y ver que formato tienen las fechas que genera. Los formatos YY-MMDD y YYYY-MM-DD HH:MM se aceptan más o menos sin problemas. CVS usa UTC (Universal Coordinate Time) para representar las fechas. Pero si no indica este desplazamiento se asume el desplazamiento local (lo cual es útil siempre que los programadores estén en la misma franja horaria). Por ejemplo, si tras preguntar por la evolución del fichero hola.c obtenemos: $ cvs log hola.c RCS file: /usr/local/share/cvsroot/saludos/hola.c,v Working file: hola.c head: 1.3 branch: locks: strict Pág 45 Gestión de versiones con CVS y Subversion macprogramadores.org access list: symbolic names: ver_inicial: 1.1.1.1 ninguno: 1.1.1 keyword substitution: kv total revisions: 4; selected revisions: 4 description: ---------------------------revision 1.3 date: 2007-06-24 19:14:47 +0200; author: flh; state: Exp; lines: +1 -1; commitid: czrAOGAQF4K8Kbns; Mete mas texto ---------------------------revision 1.2 date: 2007-06-17 11:00:03 +0200; author: flh; state: Exp; lines: +2 -1; commitid: cxq9FNQqo2piefms; Anadido copyright ---------------------------revision 1.1 date: 2007-06-16 20:01:34 +0200; author: flh; state: Exp; commitid: DG9Gf60O4Cpcfams; branches: 1.1.1; Initial revision ---------------------------revision 1.1.1.1 date: 2007-06-16 20:01:34 +0200; author: flh; state: Exp; lines: +0 -0; commitid: DG9Gf60O4Cpcfams; Esto es un proyecto de ejemplo ============================================================== Y queremos obtener el fichero hola.c el día 17 de Junio del 2007 debemos ejecutar: $ cvs update -D 2007-06-17 -p hola.c ============================================================== Checking out hola.c RCS: /usr/local/share/cvsroot/saludos/hola.c,v VERS: 1.1.1.1 *************** #include <stdio.h> int main() { printf("Hola mundo controlado en CVS"); return 0; } Hemos obtenido la revisión 1.1. Al no especificar hora, se asume que la hora son las 00:00 del día dado. Cuando obtenemos una revisión antigua de un fichero usando la opción -D se activa el sticky date, el cual para quitarlo (al igual que el sticky tag) debemos de ejecutar el comando update con la opción -A sobre el fichero. Mientras que la opción -r es más útil para obtener revisiones de ficheros individuales, la opción -D es más útil para obtener todos los ficheros del proyecto en el estado en el que se encontraban en un determinado momento de tiempo. En este caso no pasaríamos nombre de fichero al comando update. Por ejemplo, para obtener todos los ficheros del proyecto, tal como se encontraban el día 18-Junio2007 a las 18:00 haríamos: Pág 46 Gestión de versiones con CVS y Subversion macprogramadores.org $ cvs update -D "2007-6-18 18:00" cvs update: Updating . U Makefile U hola.c 5. Mezclas y conflictos En CVS (y en la mayoría de los gestores de versiones) la mezcla se hace siempre durante el update, no durante el commit. De hecho, si durante el commit se debiera de hacer una mezcla, lo que se produce realmente en CVS es un conflicto. CVS mezcla los ficheros usando un algoritmo de mezcla línea a línea. Si los cambios se han producido en distintas líneas, CVS simplemente añade, reemplaza o elimina líneas convenientemente. Si los cambios se han producido en la misma línea durante el update se produce un conflicto. De hecho, en CVS existen dos tipos de conflictos: • • Conflictos de commit. Se producen cuando un programador intenta subir cambios al proyecto del repositorio y el fichero del repositorio ha sido actualizado por otro usuario. Conflictos de update. Se producen cuando intenta mezclarse un fichero del sandbox con un fichero del repositorio, y ambos ficheros han sido actualizados en la misma línea. Resolver los conflictos de commit es sencillo: Hacemos primero un update, y (suponiendo que dos usuarios no hayan modificado el fichero en la misma línea) hacemos después un commit. Resolver un conflicto de update es más complicado, porque dos programadores han indicado distinta funcionalidad del programa en la misma línea. En consecuencia, al menos uno de los programadores debe rehusar su cambio. En este momento ambos programadores deben reunirse y discutir qué funcionalidad corresponde al programa en la línea conflictiva. Una vez acordada una solución el programador que encontró el conflicto (el último en actualizarse el fichero) debe de resolverlo. Por ejemplo, supongamos que el fichero hola.c se encuentra en la revisión 1.3 en el estado que muestra el Listado 4.1, y dos usuarios distintos modifican el mensaje de saludo tal como muestran el Listado 4.3 y Listado 4.4. int main() { printf("Buenos días mundo controlado en CVS\n"); printf("Este fichero esta mas completo que antes\n"); return 0; } Listado 4.3: Un nuevo mensaje de saludo int main() { printf("Bienvenido al mundo controlado en CVS\n"); printf("Este fichero esta mas completo que antes\n"); return 0; } Listado 4.4: Otro mensaje de saludo Pág 47 Gestión de versiones con CVS y Subversion macprogramadores.org Cuando el segundo de los usuarios intente actualizar el repositorio obtendrá el mensaje: $ cvs commit hola.c cvs commit: Up-to-date check failed for `hola.c' cvs [commit aborted]: correct above errors first! CVS le avisa de que hay cambios en el repositorio que no se ha bajado. Entonces intentará bajarse el fichero del repositorio con update: $ cvs update hola.c RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.3 retrieving revision 1.4 Merging differences between 1.3 and 1.4 into hola.c rcsmerge: warning: conflicts during merge cvs update: conflicts found in hola.c C hola.c Y obtendrá un menaje de error indicando que se ha producido un conflicto. CVS marca dentro del fichero hola.c primero la línea conflictiva tal como estaba en el sandbox, y luego la línea conflictiva tal como estaba en el repositorio: $ cat hola.c #include <stdio.h> int main() { <<<<<<< hola.c printf("Bienvenido al mundo controlado en CVS\n"); ======= printf("Buenos dias mundo controlado en CVS\n"); >>>>>>> 1.4 printf("Este fichero esta mas completo que antes\n"); return 0; } Además, en el repositorio se crea un fichero con el nombre .#file.revision (.#hola.c.1.3 en nuestro ejemplo) donde se almacena el contenido del fichero conflictivo antes de que ninguno de los dos programadores lo modificase. Después de que el programador que ha encontrado el conflicto llegue a un acuerdo con su compañero, corregirá el fichero hola.c, y subirá los cambios al repositorio con el comando commit: $ cat hola.c #include <stdio.h> int main() { printf("Buenos dias, y bienvenido al mundo controlado en CVS\n"); printf("Este fichero esta mas completo que antes\n"); return 0; } $ cvs commit hola.c /usr/local/share/cvsroot/saludos/hola.c,v <-- hola.c new revision: 1.5; previous revision: 1.4 Pág 48 Gestión de versiones con CVS y Subversion macprogramadores.org Es importante tener claro que en este ejemplo la revisión 1.3 corresponde al fichero hola.c antes de que ninguno de los dos programadores lo modificase. La revisión 1.4 corresponde al fichero después de que el primero de los programadores lo modifique, y la revisión 1.5 corresponde la fichero después de que el segundo programador corrija el conflicto. 6. Mantenimiento de ficheros del repositorio Aunque al principio de un proyecto se diseñe una estructura inicial de ficheros y directorios, es inevitable que con el tiempo esta estructura cambie. En este apartado vamos a ver cómo podemos añadir, mover o borrar los ficheros y directorios de un proyecto. 6.1. Añadir ficheros y directorios al repositorio En el apartado 8 del Tema 3 vimos que para añadir un fichero del sandbox al proyecto del repositorio primero debemos de ejecutar el comando add sobre el fichero, el cual marcaba el fichero para ser añadido al proyecto, y después ejecutamos el comando commit que transportaba el fichero del sandbox al proyecto del repositorio. Para añadir un subdirectorio al proyecto también se usa el comando add, pero a diferencia de los ficheros el subdirectorio se sube directamente al repositorio. Es decir, no es necesario ejecutar commit sobre el subdirectorio. Por otro lado, en el lado del sandbox, se crea dentro del subdirectorio añadido un subdirectorio con el nombre CVS que es donde se almacena la información de control de CVS para el subdirectorio. Los ficheros dentro de un directorio no se pueden añadir hasta que se añade el directorio que los contiene. Por ejemplo, supongamos que tenemos en el sandbox un subdirectorio llamado docs con un fichero llamado README.txt que queremos añadir proyecto del repositorio. Para ello primero añadimos el subdirectorio: $ ls docs README.txt $ cvs add docs Directory /usr/local/share/cvsroot/saludos/docs added to the repository En este momento se ha añadido el subdirectorio docs al proyecto del repositorio, y además se habrá creado en el sandbox el subdirectorio CVS: $ ls docs CVS README.txt El siguiente paso es añadir el fichero del subdirectorio: $ cd docs $ cvs add README.txt cvs add: scheduling file `README.txt' for addition cvs add: use `cvs commit' to add this file permanently $ cvs commit README.txt /usr/local/share/cvsroot/saludos/docs/README.txt,v <-- Pág 49 README.txt Gestión de versiones con CVS y Subversion macprogramadores.org initial revision: 1.1 El hecho de tener que ejecutar add y luego commit para añadir un fichero permite que podamos arrepentirnos del comando add antes de ejecutar el comando commit. En concreto, en caso de arrepentirnos podemos ejecutar el comando remove sobre el fichero que hemos marcado para que deje de estar añadido. Por ejemplo, supongamos que creamos y añadimos otro fichero llamado INSTALL.ttx: $ touch INSTALL.ttx $ cvs add INSTALL.ttx cvs add: scheduling file `INSTALL.ttx' for addition cvs add: use `cvs commit' to add this file permanently Por suerte, antes de hacer el commit nos damos cuenta de que el nombre del fichero está mal escrito, con lo que ejecutamos remove sobre el fichero para que no se añada al proyecto, lo renombramos, y lo subimos al proyecto con el nombre correcto: $ cvs remove -f INSTALL.ttx cvs remove: removed `INSTALL.ttx' La opción -f del comando remove borra el fichero del sandbox. Ahora podemos crear el fichero correcto y subirlo al proyecto con: $ touch INSTALL.txt $ cvs add INSTALL.txt cvs add: scheduling file `INSTALL.txt' for addition cvs add: use `cvs commit' to add this file permanently $ cvs commit INSTALL.txt /usr/local/share/cvsroot/saludos/docs/INSTALL.txt,v <-initial revision: 1.1 INSTALL.txt Por último, conviene comentar que la opción más utilizada del comando add es la opción -kb, la cual añade ficheros en modo binario, es decir, ficheros sobre los que no se realiza la operación de mezcla a nivel de líneas, sino que se sobrescribe el fichero cada vez que se actualiza. Por ejemplo, para añadir un fichero binario diseno.ppt al proyecto haríamos: $ cvs add -kb diseno.ppt cvs add: scheduling file `diseno.ppt' for addition cvs add: use `cvs commit' to add this file permanently $ cvs commit diseno.ppt /usr/local/share/cvsroot/saludos/docs/diseno.ppt,v <-initial revision: 1.1 6.2. diseno.ppt Borrar ficheros y directorios del repositorio El comando remove permite borrar un fichero del proyecto del repositorio. Al igual que el comando add, el comando remove sólo marca el fichero para ser eliminado. Si queremos eliminarlo del proyecto del repositorio necesitamos ejecutar el comando commit. También, al igual que el comando add, podemos eliminar la marca de remove (siempre que no hayamos ejecutado commit) volviendo a ejecutar add sobre el fichero. Pág 50 Gestión de versiones con CVS y Subversion macprogramadores.org En el apartado 3.1 adelantamos que los ficheros eliminados se guardaban en un subdirectorio especial del repositorio llamado Attic, de esta forma el fichero borrado puede ser recuperado en un futuro tal como se explicó en el apartado 4.3. Además, cuando los demás programadores ejecuten update sobre su sandbox, el fichero filename se borrará de su sandbox con el mensaje cvs server: filename is no longer in the repository. Si alguien modifica y hace un commit de los cambios del fichero que queremos borrar antes de hacer nosotros un commit, nuestro commit falla y se nos reporta el error. Si por el contrario, nosotros hacemos un commit de la orden de borrado, y luego otro usuario que ha modificado localmente el fichero hace un commit, el otro usuario será el que reciba el mensaje de conflicto. Para marcar un fichero como borrado con remove, primero debemos de borrarlo del sandbox (p.e. con el comando rm ). Podemos pasar a remove la opción de comando -f para que borre primero el fichero del sandbox, y luego lo marque para borrado. CVS no nos deja ejecutar el comando remove sobre un fichero que tenga activado el sticky tag o sticky date. Para ello, primero debemos que quitar el stricky tag o sticky date con el comando update y la opción -A . Por último, conviene recordar que, como vimos en el apartado 8 del Tema 3, CVS no nos deja borrar directorios, lo más que nos deja es dejarlos vacíos, e impedir que se descarguen los directorios vacíos al sandbox usando la opción -P en dos situaciones: (1) Cuando al comando update le pasemos la opción de comando -d, y (2) siempre que ejecutemos el comando checkout. 6.3. Mover ficheros y directorios CVS no proporciona un comando diseñado específicamente para mover ficheros o directorios. En principio podemos conseguir este efecto mediante una combinación de comandos del sistema operativo y de CVS, pero esto hace que la evolución del proyecto sea más difícil de tratar, con lo que se recomienda minimizar estos cambios y dejar constancia clara de ellos cuando se produzcan en los logs. Para mover un fichero, primero movemos el fichero dentro del sandbox, luego ejecutamos remove sobre el nombre antiguo y add sobre el nombre nuevo, y por último hacemos un commit de los cambios. Por ejemplo, para mover el fichero README.txt a NOTES.txt hacemos: $ mv README.txt NOTES.txt $ cvs remove README.txt cvs remove: scheduling `README.txt' for removal cvs remove: use `cvs commit' to remove this file permanently $ cvs add NOTES.txt cvs add: scheduling file `NOTES.txt' for addition cvs add: use `cvs commit' to add this file permanently $ cvs commit /usr/local/share/cvsroot/saludos/docs/NOTES.txt,v <-- NOTES.txt initial revision: 1.1 /usr/local/share/cvsroot/saludos/docs/README.txt,v <-- README.txt new revision: delete; previous revision: 1.1 Al ejecutar commit sin indicar nombre de fichero se suben los ficheros modificados. Además el mensaje de log que demos se habrá almacenado en ambos ficheros: $ cvs log README.txt Pág 51 Gestión de versiones con CVS y Subversion macprogramadores.org RCS file: /usr/local/share/cvsroot/saludos/docs/Attic/README.txt,v Working file: README.txt head: 1.2 branch: locks: strict access list: symbolic names: keyword substitution: kv total revisions: 2; selected revisions: 2 description: ---------------------------revision 1.2 date: 2007-06-27 18:44:03 +0200; author: flh; state: dead; lines: +0 -0; commitid: Kk4L97VZJ0jztzns; Renombrado README.txt a NOTES.txt ---------------------------revision 1.1 date: 2007-06-26 22:32:42 +0200; author: flh; state: Exp; commitid: s6sI9c3aMey2Msns; Fichero de documentacion ============================================================== $ cvs log NOTES.txt RCS file: /usr/local/share/cvsroot/saludos/docs/NOTES.txt,v Working file: NOTES.txt head: 1.1 branch: locks: strict access list: symbolic names: keyword substitution: kv total revisions: 1; selected revisions: 1 description: ---------------------------revision 1.1 date: 2007-06-27 18:44:03 +0200; author: flh; state: Exp; commitid: Kk4L97VZJ0jztzns; Renombrado README.txt a NOTES.txt ============================================================== 7. Exportar ficheros CVS crea subdirectorios con el nombre CVS en cada directorio del sandbox. Estos subdirectorios contienen metadatos que son muy útiles durante el desarrollo, pero que un usuario que no va a desarrollar código no necesita. En este apartado vamos a ver cómo obtener un proyecto "limpio" de metadatos. El comando checkout nos permite obtener un sandbox con metadatos. Por contra el comando export nos permite obtener una copia del proyecto libre de metadatos. Su sintaxis general es: cvs [global-options] export [command-options] project Este comando nos exige indicar la revisión que queremos: bien por número de revisión, o bien por fecha. Si queremos obtener la última revisión podemos usar -r HEAD o -D now. Además, debido a que este comando no se suele ejecutar desde un sandbox, normalmente tendemos que pasar la opción global -d para indicar la Pág 52 Gestión de versiones con CVS y Subversion macprogramadores.org cadena de conexión. Por ejemplo, para obtener el proyecto saludos con export podemos hacer: $ cvs -d /usr/local/share/cvsroot export -D now saludos cvs export: Updating saludos U saludos/Makefile U saludos/hola.c cvs export: Updating saludos/docs U saludos/docs/INSTALL.txt U saludos/docs/NOTES.txt U saludos/docs/diseno.ppt 8. Liberar el sandbox Cuando ya no vayamos a trabajar más con un sandbox podemos simplemente borrar el directorio del sandbox, pero se recomienda que mejor usemos el comando release sobre el sandbox. Este comando comprueba que no existan cambios en el sandbox sin subir al repositorio, y en caso de encontrarlos no libera el sandbox. $ cvs release . You have [0] altered files in this repository. Are you sure you want to release directory `.': y Este comando no borra el sandbox, sólo comprueba que no queden cambios sin subir al repositorio. La única opción de comando que podemos pasar al comando release es la opción -d, que después de comprobar que en el sandbox no quedan cambios sin subir al repositorio, borra el directorio del sandbox. 9. Los patch de proyecto Si los usuarios a los que hemos entregado un código fuente tienen acceso a nuestro CVS, cuando corrijamos errores sobre el CVS, los usuarios no tienen más que bajarse y recompilarse el nuevo código fuente. En ocasiones la política de nuestra organización impide que los usuarios tengan acceso a nuestro CVS, sino que en su lugar les entregamos el código fuente (p.e. extraído con el comando export ) y ellos se lo compilan para ejecutarlo. En caso de seguir esta política, cada vez que actualicemos el código fuente tenemos dos opciones: 1. Entregarles una nueva versión de todos los ficheros del proyecto (p.e. extraído con el comando export ). 2. Entregarles un patch del proyecto. Un patch del proyecto es un fichero donde se almacenan las diferencias entre dos versiones de un proyecto. En un patch se suelen almacenar las diferencias que tienen todos los ficheros de un proyecto, pero también se podrían almacenar las diferencias de un subconjunto de estos, o de un sólo fichero. Los sistemas UNIX incorporan el comando patch, el cual recibe un fichero de patch (normalmente con la extensión .patch ) donde se almacenan las diferencias Pág 53 Gestión de versiones con CVS y Subversion macprogramadores.org entre uno o más ficheros, y el comando patch aplica estas transformaciones al proyecto antiguo para transformarlo en el proyecto nuevo. En los siguientes subapartados vamos a ver cómo se crea un fichero de patch, tanto de forma tradicional, como usando CVS. 9.1. Crear un fichero de patch Tradicionalmente, para crear un patch se ha usado el comando diff con las opciones -Naur. Si se quiere crear un patch de un sólo fichero se puede usar el comando diff de la forma: diff -Naur oldfile newfile > file.patch Las diferencias para ir del fichero oldfile a newfile se almacenan el en fichero file.patch. Sin embargo, es más común almacenar las diferencias entre todos los ficheros del proyecto con el comando: diff -Naur olddir newdir > file.patch Donde oldir y newdir son el antiguo y nuevo directorio de proyecto. La opción -N trata a los ficheros ausentes como vacíos, la opción -a pide que se trate a todos los ficheros como ficheros de texto, la opción -u hace que se use un formato que entienda el comando patch, y la opción -r hace que la comparación se haga recursivamente en los subdirectorios. Por ejemplo, supongamos que en el subdirectorio saludosold tenemos la versión inicial que entregamos a los usuarios de nuestro proyecto, y en el subdirectorio saludos tenemos la última versión de nuestro proyecto para la que les queremos dar un fichero de patch: $ cvs -d /usr/local/share/cvsroot export -r ver_inicial saludos cvs export: Updating saludos U saludos/Makefile U saludos/hola.c $ mv saludos saludosold $ cvs -d /usr/local/share/cvsroot export -r HEAD saludos export: Updating saludos U saludos/Makefile U saludos/hola.c cvs export: Updating saludos/docs U saludos/docs/NOTES.txt Ahora podemos crear un fichero de patch que vaya de la versión inicial a la versión actual. Para ello podemos ejecutar el comando: $ diff -Naur saludosold saludos > avance.patch El fichero avance.patch contendrá una entrada por cada fichero a actualizar: $ cat avance.patch diff -Naur saludosold/docs/NOTES.txt saludos/docs/NOTES.txt --- saludosold/docs/NOTES.txt 1970-01-01 01:00:00.000000000 +0100 +++ saludos/docs/NOTES.txt 2007-06-27 18:44:03.000000000 +0200 @@ -0,0 +1 @@ Pág 54 Gestión de versiones con CVS y Subversion macprogramadores.org +Esto es un documento de ayuda diff -Naur saludosold/hola.c saludos/hola.c --- saludosold/hola.c 2007-06-29 17:00:39.000000000 +0200 +++ saludos/hola.c 2007-06-26 19:29:47.000000000 +0200 @@ -2,6 +2,7 @@ int main() { - printf("Hola mundo controlado en CVS"); + printf("Buenos dias, y bienvenido al mundo controlado en CVS\n"); + printf("Este fichero esta mas completo que antes\n"); return 0; } \ No newline at end of file La primera entrada indica que hay que crear un fichero NOTES.txt cuyo contenido es Esto es un documento de ayuda. La segunda entrada indica que en el fichero hola.c hay que borrar un mensaje y añadir otros dos mensajes. El fichero cuyo nombre está precedido por --- es el fichero antiguo, y el fichero cuyo nombre está precedido por +++ es en fichero nuevo. El aspecto que más complicaciones causa a los recién llegados a los ficheros de patch es la necesidad de que coincidan los niveles de anidamiento: Si el fichero antiguo tiene como anidamiento saludosold/docs/NOTES.txt, el fichero nuevo debe tener también dos subdirectorios en su nivel de anidamiento. En caso contrario, el fichero de patch se generará correctamente, pero el comando patch que vamos a ver en el siguiente apartado fallará. 9.2. Aplicar el fichero de patch Para aplicar el fichero de patch debemos ejecutar el comando patch al cual le pasamos por su entrada estándar el fichero de patch. El comando patch espera encontrar un subdirectorio con el nombre saludos, que es el nombre que señala con +++ el fichero de patch. En el ejemplo anterior, para aplicar el fichero de patch necesitamos tener el proyecto antiguo con el nombre saludos (y no saludosold), luego lo renombramos: $ rm -r saludos $ mv saludosold saludos Y para aplicar el fichero de patch hacemos: $ patch -p0 < avance.patch patching file saludos/docs/NOTES.txt patching file saludos/hola.c Alternativamente, podríamos haber hecho: $ cat avance.patch | patch -p0 La opción -p0 indica que en el directorio actual se espera encontrar un directorio con el nombre saludos. Muchas veces lo que se quiere es aplicar el fichero de patch desde dentro del directorio de proyecto. En este caso se usa la opción -p1 para indicar que se elimine el primer subdirectorio del nivel de anidamiento del fichero de Pág 55 Gestión de versiones con CVS y Subversion macprogramadores.org patch. Es decir, otra forma en la que podríamos haber aplicado el fichero de patch hubiera sido moviéndonos dentro del directorio saludosold: $ mv avance.patch saludosold $ cd saludosold $ patch -p1 < avance.patch patching file docs/NOTES.txt patching file hola.c Análogamente, las opciones -p2, -p3, ..., eliminarían 2, 3, .... niveles de anidamiento. 9.3. Crear un fichero de patch con CVS Tradicionalmente los ficheros de patch se crean como hemos visto en los subapartados anteriores. Si estamos trabajando con CVS, existe una forma más cómoda de crear un fichero de patch usando el comando rdiff de CVS. El comando rdiff permite comparar dos revisiones del repositorio. Usando la opción -u obtenemos la diferencia en un formato compatible con el comando patch. Su formato general es: cvs [global-options] rdiff [command-options] path Obsérvese que el comando recibe un path (y no un files ) ya que lo que hay que pasarle es el path del proyecto del repositorio. Por ejemplo, para obtener un fichero de patch que vaya de la versión inicial a la última revisión hacemos: $ cvs rdiff -u -r ver_inicial -r HEAD saludos > avance.patch Si no indicamos segundo número de revisión se usa la última revisión del repositorio (la HEAD ), con lo que también podríamos haber hecho: $ cvs rdiff -u -r ver_inicial saludos > avance.patch Si no queremos un fichero de patch de todo el proyecto, sino sólo de parte podemos indicarlo. Por ejemplo, para hacer un patch sólo del directorio docs haríamos: $ cvs rdiff -u -r ver_inicial -r HEAD saludos/docs > avance.patch 10. Ficheros binarios y wrappers Cuando un fichero se modifica en el sandbox CVS utiliza dos métodos para actualizarlo: MERGE es el modo por defecto de CVS, y consiste en mezclar línea a línea. Mientras que el modo MERGE es útil para ficheros de texto tiene graves inconvenientes con ficheros binarios debido a que CVS sustituye los finales de línea estilo UNIX (\n ), Mac OS Classic (\r ) y Windows (\r\n) de acuerdo a la plataforma donde se esté ejecutando. Esto hace que los ficheros binarios no se deban procesar en modo MERGE, ya que si no, al volverlos a recuperar, podemos recuperar en fichero binario modificado. Pág 56 Gestión de versiones con CVS y Subversion macprogramadores.org Para evitar este efecto, en el caso de los fichero binarios se debe usar el modo COPY, que siempre que se modifica un fichero en el sandbox, hace una copia completa del fichero en el proyecto del repositorio. En el apartado 6.1 adelantamos que los ficheros se podían marcar como binarios al ejecutar el comando add con la opción -kb. Esta forma de trabajar es perfectamente válida, pero resulta tediosa cuando se sube gran cantidad de ficheros al repositorio. Una forma complementaria de marcar los ficheros binarios es configurar CVS para que los ficheros con determinada extensión (p.e. .doc, .ppt, .jpeg, ...) siempre se marquen como binarios. Existen tres formas distintas de marcar las extensiones de los ficheros binarios: La primera forma es usar el fichero $CVSROOT/CVSROOT/cvswrappers del repositorio1 para almacenar la extensión de los ficheros y el modo (COPY o MERGE ) en que deben ser tratados estos ficheros. Como por defecto los ficheros se tratan en modo MERGE, sólo suele indicarse en este fichero los ficheros que se tratan en modo COPY. El Listado 4.5 muestra un ejemplo de cómo indicar que ficheros con determinada extensión deben tratarse en modo COPY. *.doc *.xsl *.ppt *.jpeg *.mpeg *.zip -m -m -m -m -m -m 'COPY' 'COPY' 'COPY' 'COPY' 'COPY' 'COPY' Listado 4.5: Ejemplo de fichero cvswrappers La segunda forma es configurar la cuenta de usuario creando un fichero .cvswrappers en el directorio home del usuario con un contenido similar al anterior. La tercera forma es pasar las opción de comando -W a al comando import en la que se indica la extensión de los ficheros que deben ser procesados como binarios. Por ejemplo: $ cvs -d /usr/local/share/cvsroot import -W "*.doc -m 'COPY'" -W "*.ppt -m 'COPY'" saludos ninguno ver_inicial Hace que los ficheros del proyecto con extensión .doc o .ppt sean importados en modo COPY. 11. Opciones de comando por defecto Si usamos una opción (global o de comando) muy a menudo, puede resultar interesante fijarla por defecto para no tener que darla cada vez que ejecutemos un comando. El fichero .cvsrc en el directorio home del usuario permite almacenar estas opciones por defecto2. El Listado 4.6 muestra un ejemplo de fichero .cvsrc donde se ha indicado que se use la opción global -q que reduce la verbosidad de CVS (aunque no tanto como 1 No debe editar este fichero directamente. En el apartado 4.1 del Tema 6 se explica cómo se editan estos ficheros. 2 En CVS no existe una forma de fijar opciones por defecto para todos los usuarios. Esto garantiza que los usuarios recién llegados se encuentran con un CVS estándar, que luego pueden personalizar. Pág 57 Gestión de versiones con CVS y Subversion macprogramadores.org Q). Además se ha indicado que el comando checkout use la opción -P (no bajar directorios vacíos), el comando update las opciones -Pd (bajar nuevos directorios, pero no directorios vacíos), y los comandos diff y rdiff usen la opción -u (para indicar el formato de la comparación) cvs -q checkout -P update -dP diff -u rdiff -u Listado 4.6: Ejemplo de fichero .cvsrc Por último, comentar que podemos pedir a CVS que ignore el fichero .cvsrc usando la opción global -f. Pág 58 Gestión de versiones con CVS y Subversion macprogramadores.org Tema 5 Tagging y branching en CVS Sinopsis: El tagging permite etiquetar un número de revisión para que en un futuro resulte más sencillo referirse a la revisión. El branching consiste en crear varias versiones paralelas de un mismo código fuente. Una de estas revisiones la línea principal de desarrollo será el tronco, y las demás serán ramas. En este tema vamos a estudiar cómo se crean tags y ramas, y cómo combinar el contenido de las ramas, por ejemplo, para añadir su contenido al tronco. Pág 59 Gestión de versiones con CVS y Subversion 1. macprogramadores.org Tagging Como vimos en el apartado 4 del Tema 4, CVS nos permite recuperar cualquier revisión de un fichero. Aunque recuperar una revisión individual de un ficho es útil, resulta más útil todavía poder recuperar el conjunto de ficheros que formaban el proyecto en un determinado momento (hito) del proyecto. El tagging permite poner un tag a las revisiones de un conjunto de ficheros con el fin de poderlos recuperar en un futuro. Hay que tener en cuenta que el número de revisión no es útil en este escenario, ya que como muestra la Figura 1.2, en un momento dado, un conjunto de ficheros pueden tener números de revisiones distintas. En el apartado 4.4 del Tema 4 vimos que también podemos obtener las revisiones de un fichero en un momento dado usando la fecha, pero un tag siempre es más descriptivo para poder referirse a un hito pasado. Los nombres de los tags deben empezar por una letra, y pueden contener guiones bajos (_ ) y medios (- ). Cada tag debe de ser único para un fichero. Para poder tags usamos los comandos tag y rtag que vamos a ver en los apartados 1.1 y 1.2. Existen dos nombres de tag reservados. El nombre BASE lo usa CVS para referirse a la última revisión del sandbox sincronizada con el repositorio, y el nombre HEAD lo usa CVS para referirse a la última revisión del repositorio. Por ejemplo, imagine que está trabajando con un compañero, y ambos se bajan la revisión 1.23 del fichero main.c del proyecto del repositorio, y después su compañero envía dos nuevas revisiones del fichero main.c. En este caso usted tendrá como revisión BASE la 1.23 porque no ha actualizado su sandbox, sin embargo tendrá como revisión HEAD la 1.25, que es la última revisión en el repositorio. Sin embargo, su compañero tendrá como BASE y como HEAD la revisión 1.25, ya que su compañero sí que tendrá el repositorio actualizado. 1.1. Tagging en el sandbox Podemos usar el comando tag para poner una etiqueta a los ficheros del sandbox. Este comando marca los ficheros del sandbox en base a su BASE (no en base a su HEAD), es decir, si algún fichero ha sido actualizado en el repositorio y nosotros no nos hemos actualizado, la marca se pone en la revisión que tengamos en el sandbox. Esto permite garantizar que la etiqueta siempre se pone sobre ficheros sobre los que hayamos tenido tiempo para estudiar su contenido en el sandbox. Hay que tener en cuenta que también se ignoran los posibles cambios que hubiéramos hecho sobre el sandbox, pero que no hubiéramos subido al proyecto del repositorio, ya que la revisión BASE es la última revisión que nos bajamos del proyecto del repositorio. La sintaxis de este comando es: cvs [global-options] tag [command-options] tagname [files] El parámetro tagname es la etiqueta a asignar, y lógicamente es obligatorio indicar su valor. Si no se especifica files se asigna la etiqueta a todos los ficheros del directorio y subdirectorios de forma recursiva. Podemos usar la opción de comando l si no queremos que se asigne la etiqueta a los ficheros de los subdirectorios. La opción de comando -c permite que el comando tag compruebe si los ficheros del sandbox han sido modificados localmente (y no se les ha hecho un commit) antes de asignar las etiquetas. En este caso el comando tag falla indicando que se Pág 60 Gestión de versiones con CVS y Subversion macprogramadores.org ha producido esta circunstancia. Por ejemplo, para etiquetar los ficheros del repositorio tal como se encuentran en este momento podemos ejecutar el comando: $ cvs tag ver_2 cvs tag: Tagging . T Makefile T hola.c cvs tag: Tagging docs T docs/INSTALL.txt T docs/NOTES.txt T docs/diseno.ppt A diferencia de otros comandos, con el comando tag no tenemos que hacer un commit para que las etiquetas se suban al repositorio. También podemos usar las opciones -r y -D para poner una etiqueta por revisión o por fecha, pero esta forma de trabajar es menos común. Podemos comprobar los tags que tiene un fichero con el comando status y la opción de comando -v: $ cvs status -v hola.c ============================================================== File: hola.c Status: Up-to-date Working revision: 1.5 Tue Jun 26 17:29:47 2007 Repository revision: 1.5 /usr/local/share/cvsroot/saludos/hola.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) Existing Tags: ver_2 ver_inicial ninguno (revision: 1.5) (revision: 1.1.1.1) (branch: 1.1.1) Los tags ver_inicial y ninguno corresponden al vendor_tag y release_tag que usamos durante el comando import en el apartado 4 del Tema 3. 1.2. Tagging en el repositorio El comando rtag permite poner tags a los ficheros tal como se encuentran en el repositorio, es decir, sin tener en cuenta los ficheros del sandbox. El formato del comando es: cvs [global-options] rtag command-options tagname files Debido a que no se tienen en cuenta los ficheros del sandbox, es necesario utilizar la opción -r o -D para indicar a qué revisión de los ficheros nos estamos refiriendo, con lo que, a diferencia del comando tag, ahora el parámetro command-options es obligatorio. También lo es el parámetro files debido a que no se tiene en cuenta el sandbox para decidir dónde poner el tag, luego hay que especificarlo. Por, ejemplo, una forma alternativa al comando tag del ejemplo del apartado anterior sería: $ cvs -d /usr/local/share/cvsroot rtag -r HEAD ver_2 saludos cvs rtag: Tagging saludos Pág 61 Gestión de versiones con CVS y Subversion macprogramadores.org Obsérvese que hemos indicado la revisión HEAD, que no tiene porqué coincidir con la revisión BASE que usaba el ejemplo del apartado anterior. También obsérvese que hemos indicado con la opción -d la dirección del proyecto del repositorio. Esto se debe a que rtag puede ejecutarse desde fuera del sandbox. Sin embargo, si ejecutáramos rtag desde dentro del sandbox podríamos haber omitido la opción -d , y hubiera usado el proyecto del repositorio del sandbox (que recuérdese que está almacenada en los ficheros del subdirectorio de metadatos CVS). De nuevo no es necesario hacer un update para poder consultar el tag que hemos puesto a los ficheros: $ cvs status -v hola.c ============================================================== File: hola.c Status: Up-to-date Working revision: 1.5 Tue Jun 26 17:29:47 2007 Repository revision: 1.5 /usr/local/share/cvsroot/saludos/hola.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) Existing Tags: ver_2 ver_inicial ninguno (revision: 1.5) (revision: 1.1.1.1) (branch: 1.1.1) Por último, comentar que podemos usar la opción -D (en lugar de -r) para indicar las revisiones de los ficheros por fecha. En este caso, recuérdese que si no se indicar hora en la fecha, por defecto se asume las 00:00 del día dado. 1.3. Obtener ficheros etiquetados Para obtener un conjunto de ficheros etiquetados debemos usar la opción -r tagname junto con el comando export, checkout o update. El comando export se utiliza para obtener los ficheros del proyecto sin metadatos, checkout se usa para obtener un nuevo sandbox, mientras que el comando update se utiliza para actualizar el sandbox actual. El comando export con la opción -r lo usarán principalmente los usuarios que quieran recuperar una revisión que hayamos marcado como hito estable. Lo cual puede ser más deseable que obtener el último contenido del repositorio que podría no funcionar correctamente. Si usamos update para actualizar el sandbox con el contenido de una revisión anterior, los ficheros obtienen la marca de sticky tag y no se deben modificar hasta que eliminemos la marca de sticky tag con el comando update y la opción -A . En consecuencia el comando update con la opción -r se usa principalmente para recuperar una revisión antigua de uno o más ficheros. La misma marca de sticky tag obtenemos cuando usamos checkout para recuperar una revisión anterior. 1.4. Borrar y mover tags Normalmente, una vez que se usan tags para marcar el estado del proyecto en un determinado momento de tiempo, no se suelen cambiar. Sin embargo, a veces puede surgir la necesidad de borrar, renombrar o mover un tag. Debe tener en cuenta que modificar los tags es una operación delicada porque no queda constancia Pág 62 Gestión de versiones con CVS y Subversion macprogramadores.org de estas operaciones en el repositorio, es decir, una vez modificados no podemos deshacer los cambios o conocer el estado anterior de asignación de tags. Existen un tipo especial de tags llamados branch tags, que estudiaremos en el apartado 2. Cuando intentamos modificar estos tags CVS produce un mensaje de error y no los modifica. Sin embargo, podemos usar la opción -B para forzar la modificación de los branch tags1. 1.4.1. Borrar tags El caso más común por el que podemos necesitar borrar un tag es porque nos hayamos equivocado al ponerlo. En este caso podemos necesitar borrarlo y volverlo a poner. Para ello usamos la opción -d con: cvs tag -d tagname [files] o bien: cvs rtag -d tagname files El comando tag debe ejecutarse dentro del sandbox, y por defecto modifica el tag de todos los ficheros del directorio del sandbox y sus subdirectorios. El comando rtag exige que indiquemos en files donde borrar el tag, y si se ejecuta desde fuera del sandbox debemos de indicar también la cadena de conexión. Por ejemplo, para borrar el tag ver_2 que asignamos en los apartados anteriores podríamos hacer: $ cvs tag -d ver_2 cvs tag: Untagging . D Makefile D hola.c cvs tag: Untagging docs D docs/INSTALL.txt D docs/NOTES.txt D docs/diseno.ppt 1.4.2. Mover tags Es muy común mantener un tag a la última versión estable del proyecto. A este tag se le suele llamar release o stable. El tag puede ser usado por los usuarios del comando export para obtener siempre acceso a versiones estables de nuestro proyecto. Sin embargo este es un ejemplo de tag que es necesario modificar según el proyecto evoluciona. En estos casos podemos usar la opción de comando -F, la cual pide que mueva el tag si ya existe (en vez de producir un mensaje de error). Lógicamente esta opción se puede usar tanto con el comando tag como rtag. Por ejemplo, supongamos que hemos modificado el fichero hola.c con un cambio de última hora que queremos que forme parte de la ver_2. Para ello podemos hacer: $ cvs tag -F ver_2 hola.c T hola.c 1 Tenga mucho cuidado al modificar los branch tags, o podría destrozar la rama de ese branch tags sin posibilidad de recuperarla. Pág 63 Gestión de versiones con CVS y Subversion macprogramadores.org Hemos indicado el nombre del fichero a modificar, pero aunque no lo hubiéramos indicado el único tag que se hubiera modificado es el de hola.c, porque es el único fichero que ha sufrido cambios. 1.5. Borrar o mover tags de ficheros borrados Recuérdese que los ficheros borrados se almacenan en un subdirectorio Attic del proyecto del repositorio. Estos ficheros borrados pueden mantener tags antiguos que hayamos borrado, movido o renombrado. Debido a que estos ficheros están borrados, y a que el comando tag actúa sobre los ficheros del sandbox, este comando nunca afecta a los ficheros borrados. Afortunadamente podemos usar el comando rtag con la opción -a, la cual hace que se apliquen las opciones -d o -F a los ficheros borrados. 1.6. Renombrar tags Si algún desarrollador ha puesto un tag a los ficheros del proyecto que no cumple con la política de nombres de tags de la organización, podemos renombrar el tag. CVS no incluye ningún comando para renombrar tags, pero podemos usar la opción -r con el nombre de tag antiguo para referirnos a las revisiones en las que poner el nuevo tag, y a continuación podemos borrar el nombre de tag antiguo con la opción -d. Por ejemplo, imaginemos que nos dicen que la política de nuestra organización es que no se deben usar abreviaturas en los nombres de los tags, con lo que en vez de llamar al tag ver_2 deberíamos haberlo llamado version_2. Podemos corregir este aspecto usando el tag antiguo para indicar las revisiones en las que poner el nuevo: $ cvs tag -r ver_2 version_2 cvs tag: Tagging . T Makefile T hola.c cvs tag: Tagging docs T docs/INSTALL.txt T docs/NOTES.txt T docs/diseno.ppt Y luego borramos el tag antiguo: $ cvs tag -d ver_2 cvs tag: Untagging . D Makefile D hola.c cvs tag: Untagging docs D docs/INSTALL.txt D docs/NOTES.txt D docs/diseno.ppt 1.7. Stickiness Decimos que un fichero es striky cuando tiene un estado persistente en el repositorio distinto al estado por defecto. Hay varias causas por las que un fichero pueda ser stricky. Una es que hayamos obtenido el fichero usando update con la Pág 64 Gestión de versiones con CVS y Subversion macprogramadores.org opción -r para indicar un número de revisión o tag distinto de HEAD, en este caso el fichero tiene un stricky tag. Una segunda causa es que hayamos obtenido el fichero usando update con la opción -D y una fecha distinta de now, en este caso el fichero tiene un stricky date. Otra causa es que, como veremos en el próximo apartado, el fichero pertenezca a una rama distinta del tronco, en este caso el fichero tiene un stricky branch. Una última causa es que hayamos marcado al fichero como binario, en ese caso tendrá un stricky options con el valor -kb. El estado stricky aplica a todos los comandos ejecutados sobre un fichero. Los ficheros con un stricky tag o stricky date no deben de modificarse. Los ficheros con un stricky branch sí que pueden modificarse, pero todos los commit y update que ejecutemos sobre ellos serán relativos a la rama en la que estén. Por último, los ficheros con la marca de stricky options pueden modificarse, pero la forma en que se actualiza el repositorio pasa del modo MERGE al modo COPY. Las cuatro marcas de stricky pueden verse con el comando status. Por ejemplo, al ejecutar el siguiente comando vemos que el fichero diseno.ppt tiene la marca de stricky options: $ cvs status docs/diseno.ppt ============================================================== File: diseno.ppt Status: Up-to-date Working revision: 1.1 Tue Jun 26 20:46:41 2007 Repository revision: 1.1 /usr/local/share/cvsroot/saludos/docs/diseno.ppt,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: -kb Los subdirectorios del sandbox también pueden tener marcas de stricky, y si el subdirectorio tiene una marca de stricky, esta marca se aplica por defecto a todos los ficheros que añadamos al subdirectorio. Para eliminar las marcas de stricky tag o stricky date podemos usar el comando update con la opción -A, pero no podemos eliminar de esta forma las marcas de stricky tags o stricky options. 2. Branching En el apartado 4 del Tema 2 definimos las ramas como líneas paralelas de desarrollo en nuestro proyecto. El tronco (trunk) era la línea principal de desarrollo, y las ramas (branches) eran variantes del tronco. CVS construye las ramas y en tronco a partir del mismo código fuente, hasta que llegado un momento dado tronco y ramas divergen. Este momento es lo que llamamos la base de la rama. A partir de este momento CVS almacena los cambios hechos en la rama de forma separada a los cambios hechos al tronco. Además a las revisiones de la rama se le asignan sus propios números de revisión, basándose en el número de revisión que tiene la base de la rama. La Figura 2.1 (b) muestra cómo se crean revisiones de la rama a partir de la base de la rama. Podemos crear una rama a partir de un sólo fichero, de un conjunto de ficheros, o a partir de todo el proyecto, al igual que podemos poner tags a ficheros individuales o a grupos de ficheros. En general se recomienda hacer un branch de todos los ficheros del proyecto. Por experiencia se sabe que si se hace un branch de un fichero, al final se acaba necesitando hacer un branch de otros ficheros del proyecto, Pág 65 Gestión de versiones con CVS y Subversion macprogramadores.org y resulta más sencillo seguir la pista de un branch de todo un proyecto, que ir identificando a qué ficheros hemos hecho branch y cuáles no. Vimos que un tag marca una revisión de cada fichero al que hayamos puesto el tag, y que cuando hacíamos un update o checkout usando un tag, los ficheros obtenidos no se debían de modificar. Por el contrario, un branch crea revisiones que se pueden editar, subir y bajar al proyecto del repositorio, o asignar nuevos tag. El espacio de nombres de tags y branches es único para CVS, es decir, a un fichero no le podemos asignar un tag y un branch con el mismo nombre. Las ramas son independientes del tronco, sin embargo, tanto el tronco como las ramas se almacenan en el mismo fichero a nivel de repositorio, y los comandos que actúan sobre un fichero a nivel de repositorio pueden afectar tanto al tronco como a las ramas. Por ejemplo, comandos como status o log son comandos a nivel de repositorio, y muestran el estado del fichero tanto para el tronco como para las ramas. Por contra, para los comandos a nivel de sandbox no es lo mismo estar trabajando con el tronco que con una rama: Para estos comandos, la diferencia entre estar trabajando con el tronco o con una rama es que cuando trabajamos con una rama en el repositorio los ficheros tienen asignado el sticky branch. Una diferencia importante entre los branches y los tags es que los branches representan múltiples revisiones, mientras que los tags representan una única revisión. Por ejemplo, en la Figura 2.1 (b), un tag podría etiquetar la revisión 1.19 o la revisión 1.19.2.2, mientras que el branch tendría como número de revisión la 1.19.2 para de esa forma representar a todas las revisiones con numeración 1.19.2.x. Algunos comandos como diff necesitan que un número de revisión de branch (p.e. 1.19.2) se resuelva en una única revisión (p.e. 1.19.2.3 en la Figura 2.1 (b)), en este caso se resuelve por la revisión más moderna de la rama. En el apartado 4.3 del Tema 2 describimos cuáles eran las finalidades de los branches. Por ejemplo permitían crear versiones experimentales, o bien sacar fuera de desarrollo una versión del software para ser testeada en una rama. En general conviene saber que queremos crear un branch antes de empezar a modificar el sandbox. En el siguiente apartado veremos cómo se crea este tipo de branches. Pero también podría ocurrir que nos demos cuenta de que lo que estamos haciendo debería haberse hecho en un branch aparte (p.e. por ser un cambio experimental) cuando ya hemos modificado el sandbox. En el apartado 2.3 veremos cómo se crear un branch retroactivo. Los cambios hechos en el tronco se pueden aplicar a una rama, y los cambios hechos en una rama se pueden aplicar al tronco. Una vez aplicados estos cambios, la rama se puede abandonar, o continuar el desarrollo en función del propósito de la rama. En el apartado 4.4 del Tema 2 comentamos estos aspectos. 2.1. Crear una rama Antes de crear un branch es muy recomendable asignar un tag a la base del tronco. Esto nos permitirá mezclar el contenido del tronco y de la rama más adelante. Es muy común usar los nombres de tag pre-branch-xxx y branch-xxx para etiquetar respectivamente la base de la rama y la rama. Supongamos que se está evaluando la posibilidad de portar nuestro software al inglés, para lo cual se desea crear un branch donde realizar este trabajo, y decidir después si el software será finalmente portado o no. Para ello, lo primero que hacemos es etiquetar la base de la rama que queremos crear: $ cvs tag pre-branch-ingles cvs tag: Tagging . Pág 66 Gestión de versiones con CVS y Subversion macprogramadores.org T Makefile T hola.c cvs tag: Tagging docs T docs/INSTALL.txt T docs/NOTES.txt T docs/diseno.ppt Recuerde que al no especificar (con la opción -r) revisión sobre la que aplicar el comando tag, este se aplica sobre BASE, y no sobre HEAD, lo cual significa que si otro usuario ha modificado el proyecto del repositorio, estos cambios se ignoran desde el punto de vista del tag que ponemos a la base de la rama. También se ignoran los posibles cambios que hayamos hecho sobre el sandbox, ya que la revisión BASE es la última que hayamos bajado del proyecto del repositorio. Para crear una rama, al igual que para asignar un tag, usamos los comandos tag o rtag, pero añadiendo la opción de comando -b, que indica que el tag es de tipo branch. Por ejemplo, ahora podríamos crear el branch con el comando: $ cvs tag -b branch-ingles cvs tag: Tagging . T Makefile T hola.c cvs tag: Tagging docs T docs/INSTALL.txt T docs/NOTES.txt T docs/diseno.ppt En caso de no estar creando el branch sobre la revisión BASE, o en caso de estar usando el comando rtag, además de la opción de comando -b tendríamos que haber proporcionado la opción -r para indicar la base de la rama. Es decir, tendríamos que haber hecho algo como: $ cvs tag -r pre-branch-ingles -b branch-ingles Ahora podemos consultar los tags que tenemos sobre uno de nuestros ficheros con el comando: $ cvs status -v hola.c ============================================================== File: hola.c Status: Up-to-date Working revision: 1.5 Tue Jun 26 17:29:47 2007 Repository revision: 1.5 /usr/local/share/cvsroot/saludos/hola.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) Existing Tags: branch-ingles pre-branch-ingles ver_2 ver_inicial ninguno (branch: 1.5.2) (revision: 1.5) (revision: 1.5) (revision: 1.1.1.1) (branch: 1.1.1) Pág 67 Gestión de versiones con CVS y Subversion macprogramadores.org Obsérvese que el tag branch-ingles aparece marcado como un tag de branch, mientras que el tag pre-branch-ingles aparece marcado como un tag de revisión. También observamos que los ficheros que se usaron para crear el proyecto del repositorio inicial (p.e. hola.c ) tienen un tag de branch para el vendor_tag que usamos en el comando import (ninguno en nuestro ejemplo), y otro tag de revisión para el release_tag (ver_inicial en nuestro ejemplo). En el apartado 2.9 se verá para qué sirven estos tag. Estos tags no aparecen en los ficheros que hayamos añadido más tarde al proyecto. Por ejemplo: $ cvs status -v docs/diseno.ppt ============================================================== File: diseno.ppt Status: Up-to-date Working revision: 1.1 Tue Jun 26 20:46:41 2007 Repository revision: 1.1 /usr/local/share/cvsroot/saludos/docs/diseno.ppt,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: -kb Existing Tags: branch-ingles pre-branch-ingles ver_2 (branch: 1.1.2) (revision: 1.1) (revision: 1.1) Por último comentar que la creación del branch ocurre en el repositorio, no en el sandbox. Lo cual significa que en este momento seguimos situados en el tronco del proyecto, y los cambios que subamos o bajemos del repositorio serán para el tronco, no para la rama. En el siguiente apartado veremos cómo empezar a trabajar en una rama. 2.2. Activar la rama en el sandbox Para pasar a trabajar con la rama debemos ejecutar el comando checkout o update con la opción de comando -r branch-xxx. Esto marca los ficheros del sandbox con un sticky branch para el tag branch-xxx. Por ejemplo, en el sandbox del ejemplo anterior haríamos: $ cvs update -r branch-ingles cvs update: Updating . cvs update: Updating docs Ahora en todos los ficheros del proyecto se habrá activado el stricky branch: $ cvs status hola.c ============================================================== File: hola.c Status: Up-to-date Working revision: 1.5 Tue Jun 26 17:29:47 2007 Repository revision: 1.5 /usr/local/share/cvsroot/saludos/hola.c,v Sticky Tag: branch-ingles (branch: 1.5.2) Sticky Date: (none) Sticky Options: (none) Pág 68 Gestión de versiones con CVS y Subversion macprogramadores.org Y todos los commit y update que hagamos serán respecto a la rama, y no al tronco. Sin embargo los comandos de repositorio siguen dando información sobre todo el fichero, no sólo sobre la rama en cuestión. Por ejemplo, el comando status ejecutado sobre un fichero de una rama da información tanto sobre el tronco como sobre la rama. También es posible activar el stricky branch sólo para algunos ficheros del sandbox, de esta forma algunos ficheros se actualizarán respecto a la rama y otros respecto al tronco, pero está forma de trabajar no se recomienda. Para pasar a trabajar sobre el tronco, podemos ejecutar le comando update con la opción de comando -A, la cual desactiva el stricky branch. 2.3. Ramas retroactivas En caso de que cuando estemos modificando el contenido del repositorio, pero antes de haber hecho commit de los cambios, nos demos cuenta de que los cambios que hemos hecho deberían estar en una rama, todavía podemos crear una rama siguiendo el siguiente proceso: Primero, hacemos un backup del sandbox con el comando de copia del sistema operativo (p.e. cp). Esto es importante porque no podemos hacer commit de los cambios hasta haber creado la rama. Segundo, usamos los comandos: $ cvs tag pre-branch-xxx $ cvs tag -b branch-xxx Para, respectivamente, etiquetar la base de la rama y crear la rama. El comando tag pone las etiquetas sobre la revisión BASE, que es la revisión sobre la que queremos crear la rama, e ignora los cambios que hayamos hecho en el sandbox. Recuérdese que el comando tag no modifica el sandbox, sólo pone las etiquetas en el repositorio. Tercero, usamos el comando: $ cvs update -r branch-xxx Para cambiar desde el tronco a la rama. Al ejecutar este comando se activa el stricky branch. Este comando intenta aplicar los cambios de la rama a los ficheros de la revisión del sandbox, pero como los ficheros del sandbox tienen la misma revisión, esta operación nunca modifica el contenido de los ficheros del sandbox. Eso sí, conserva los cambios que hayamos hecho en el sandbox (los cuales no tienen revisión asignada). Cuarto, ejecutamos el comando commit para subir los cambios del sandbox al siguiente número de revisión de la rama. 2.4. Ramas en revisiones anteriores Es también posible crear ramas que nazcan en revisiones pasadas. Para ello primero bajamos la revisión a partir de la que queremos crear la rama, usando el comando update con -r nombre_tag o -D fecha, y a partir de ese momento creamos la rama con los comandos: $ cvs tag pre-branch-xxx $ cvs tag -b branch-xxx Pág 69 Gestión de versiones con CVS y Subversion macprogramadores.org $ cvs update -r branch-xxx En este caso no podemos borrar las revisiones que ya hayan sido guardadas en el tronco, pero podemos añadir nuevas revisiones a la rama que hemos creado. 2.5. Añadir y borrar ficheros Cuando estamos en un sandbox de rama, es posible añadir o borrar ficheros a una rama con los comandos add y remove. En este caso los ficheros sólo se añaden o borrar a la rama en la que estamos situados, y no afectan al tronco. Por ejemplo, para añadir a nuestro sandbox de rama un nuevo fichero, con información de quién y cómo ha hecho la traducción, podemos usar los comandos: $ cvs add docs/traductor.txt cvs add: scheduling file `docs/traductor.txt' for addition on branch `branch-ingles' cvs add: use 'cvs commit' to add this file permanently $ cvs commit Checking in docs/traductor.txt; /usr/local/share/cvsroot/saludos/docs/Attic/traductor.txt,v <-traductor.txt new revision: 1.1.2.1; previous revision: 1.1 done Obsérvese que el fichero traductor.txt, al ser sólo de rama, se guarda en el subdirectorio Attic del proyecto del repositorio. Y es un fichero que sólo existe para la rama: $ cvs status docs/traductor.txt ============================================================== File: traductor.txt Status: Up-to-date Working revision: 1.1.2.1 Sun Jul 15 06:39:50 2007 Repository revision: 1.1.2.1 /usr/local/share/cvsroot/saludos/docs/Attic/traductor.txt,v Sticky Tag: branch-ingles (branch: 1.1.2) Sticky Date: (none) Sticky Options: (none) 2.6. Mezclar ramas En el apartado 4.2 del Tema 2 comentamos que existían dos formas principales de mezclar ramas: (1) Aplicar la rama al tronco, que aplicaba los cambios que había sufrido la rama (desde que se separó del tronco) al tronco. (2) Aplicar el tronco a la rama que aplicaba los cambios que había sufrido el tronco (desde que la rama se separó del tronco) a la rama. El aplicar los cambios en un sentido o en otro depende de la política que estemos siguiendo para la gestión del repositorio. En el apartado 4.3 y 4.4 del Tema 2 se discutieron con detalle estas políticas. Independientemente de la dirección en que se apliquen los cambios, una vez que se mezcla el tronco con una rama (o dos ramas entre sí), la rama que se aplica permanece sin cambios, y la rama sobre la que se aplica cambia. En caso de estar usando ramas largas (véase apartado 4.4 del Tema 2) conviene etiquetar los puntos hasta donde se han cogido revisiones de la rama origen para pasarlas a la rama destino. De esta forma, cuando la rama origen evoluciona, ya no cogeremos todos Pág 70 Gestión de versiones con CVS y Subversion macprogramadores.org los cambios desde la base de la rama, sino desde el punto donde colocamos la etiqueta. En concreto: • • • Si los cambios se aplican de la rama al tronco — véase Figura 5.1 (a) — pondremos etiquetas en la rama para la primera vez aplicar los cambios desde pre_branch_xxx a tag1, la segunda vez desde tag1 a tag2, ... Si los cambios se aplican del tronco a la rama — véase Figura 5.1 (b) — podremos etiquetas en el tronco para la primera vez aplicar los cambios desde pre_branch_xxx a tag1, la segunda vez desde tag1 a tag2, ... Si los cambios se aplican en ambos sentidos, deberemos poner las etiquetas en la rama desde la que se aplican los cambios. En el ejemplo de la Figura 5.1 (c), primero se aplican desde la rama al tronco los cambios que van desde pre_branch_xxx a tag1, luego se aplican desde el tronco a la rama los cambios desde pre_branch_xxx hasta tag2, y por último se aplican desde la rama al tronco los cambios desde tag1 a tag3. tag1 pre_branch_xxx tag2 tag3 rama mezcla mezcla mezcla mezcla mezcla tronco (a) Aplicar la rama al tronco pre_branch_xxx rama mezcla tronco tag1 tag2 tag3 (b) Aplicar el tronco a la rama pre_branch_xxx rama tag1 tag3 mezcla mezcla mezcla tronco tag2 (c) Aplicar en ambos sentidos Figura 5.1: Etiquetas al mezclar ramas largas Cuando el trabajo que se está haciendo en una rama acaba, parece lógico eliminar o cerrar esta rama. Sin embargo CVS no permite borrar o cerrar ramas, sino que quedan como parte de la historia del proyecto. Lo más que podemos hacer sobre una rama que no vamos a usar más es apuntar un mensaje de log donde se indique por qué se da por cerrada la rama. Al ir a escribir este mensaje de log, podemos darnos cuenta de que CVS no nos deja hacer commit si no modificamos el contenido de los ficheros del proyecto. Esto en principio nos obligaría a modificar todos los ficheros del proyecto para que todos los ficheros aceptaran el mensaje de log. Una forma de poder crear una nueva revisión en los ficheros del proyecto sin necesidad de modificarlos es con la opción de comando -f, que pasamos a commit para forzar Pág 71 Gestión de versiones con CVS y Subversion macprogramadores.org el que se cree una nueva revisión de los ficheros, aunque los ficheros no hayan sufrido cambios. 2.6.1. Aplicar cambios desde el tronco a la rama. Supongamos que el tronco se ha corregido un bug mientras que en paralelo en la rama (que creamos en el apartado 2.1) se está procediendo a la traducción de los mensajes al inglés. Los cambios del tronco no se trasladan automáticamente a la rama, sino que tendremos que aplicar los cambios del tronco a la rama. Para ello nos situaremos en el sandbox de la rama y ejecutaremos el comando: $ cvs update -j pre-branch-ingles -j HEAD cvs update: Updating . RCS file: /usr/local/share/cvsroot/saludos/Makefile,v retrieving revision 1.1.1.1 retrieving revision 1.2 Merging differences between 1.1.1.1 and 1.2 into Makefile RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.5 retrieving revision 1.6 Merging differences between 1.5 and 1.6 into hola.c cvs update: Updating docs La etiqueta HEAD siempre se usa para referirse a la cabeza del tronco. En el próximo apartado veremos cómo referirnos a la cabeza de una rama. El comando coge los cambios que van desde pre-branch-ingles hasta HEAD y los pasa a la rama. En concreto, se han pasado a la rama los cambios de dos ficheros del tronco: (1) Makefile desde la versión inicial 1.1.1.1 (pre-branch-ingles) a la versión 1.2 (HEAD), y (2) hola.c desde la versión 1.5 (pre-branch-ingles) a la 1.6 (HEAD). Los cambios en el sandbox debemos almacenarlos en el repositorio haciendo un commit de la rama: $ cvs commit Checking in Makefile; /usr/local/share/cvsroot/saludos/Makefile,v <-- Makefile new revision: 1.1.1.1.2.1; previous revision: 1.1.1.1 done Checking in hola.c; /usr/local/share/cvsroot/saludos/hola.c,v <-- hola.c new revision: 1.5.2.2; previous revision: 1.5.2.1 done Ahora conviene etiquetar el tronco: $ cvs update -A cvs update: Updating . RCS file: /usr/local/share/cvsroot/saludos/Makefile,v retrieving revision 1.1.1.1 retrieving revision 1.2 Merging differences between 1.1.1.1 and 1.2 into Makefile Makefile already contains the differences between 1.1.1.1 and 1.2 RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.5 retrieving revision 1.6 Merging differences between 1.5 and 1.6 into hola.c hola.c already contains the differences between 1.5 and 1.6 cvs update: Updating docs Pág 72 Gestión de versiones con CVS y Subversion macprogramadores.org cvs update: docs/traductor.txt is no longer in the repository $ cvs tag to-branch-ingles cvs tag: Tagging . T Makefile T hola.c cvs tag: Tagging docs T docs/INSTALL.txt T docs/NOTES.txt T docs/diseno.ppt Para así la próxima vez que queramos pasar cambios del tronco a la rama ejecutar desde el sandbox de la rama el comando: $ cvs update –j to-branch-ingles –j HEAD 2.6.2. Aplicar de la rama al tronco Supongamos ahora que el traductor de la rama ha terminado de traducirla, y ha enviado los cambios en la rama al proyecto del repositorio: $ cvs commit Además, el consejo de dirección se ha reunido y ha decidido aprobar la traducción del programa al inglés. Luego, es hora de pasar los cambios que el traductor hizo en la rama al tronco. Para ello, nos situamos en el sandbox del tronco y ejecutamos el comando: $ cvs update -j pre-branch-ingles -j branch-ingles cvs update: Updating . RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.5 retrieving revision 1.5.2.1 Merging differences between 1.5 and 1.5.2.1 into hola.c cvs update: Updating docs RCS file: /usr/local/share/cvsroot/saludos/docs/INSTALL.txt,v retrieving revision 1.1 retrieving revision 1.1.2.1 Merging differences between 1.1 and 1.1.2.1 into INSTALL.txt RCS file: /usr/local/share/cvsroot/saludos/docs/NOTES.txt,v retrieving revision 1.1 retrieving revision 1.1.2.1 Merging differences between 1.1 and 1.1.2.1 into NOTES.txt U docs/diseno.ppt U docs/traductor.txt En este caso indicamos que queremos pasar al tronco los cambios que van desde la base de la rama (pre-branch-ingles) hasta la cabeza de la rama (branchingles). Recuérdese que HEAD sólo se usa para referirse a la cabeza del tronco. Para referirse a la cabeza de la rama se usa el nombre de la rama (branch-ingles ). Obsérvese que el fichero traductor.txt no existía en el tronco, pero sí en la rama, con lo que de cara al tronco el fichero no forma parte del sandbox: $ cvs status docs/traductor.txt ============================================================== File: traductor.txt Status: Locally Added Working revision: New file! Pág 73 Gestión de versiones con CVS y Subversion macprogramadores.org Repository revision: 1.1 /usr/local/share/cvsroot/saludos/docs/Attic/traductor.txt,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) Aunque sí que ha sido añadido automáticamente (comando add), no se ha hecho un commit del fichero al tronco. Ya sólo queda hacer un commit de los cambios en el sandbox del tronco con el comando: $ $ cvs commit Checking in hola.c; /usr/local/share/cvsroot/saludos/hola.c,v <-- hola.c new revision: 1.7; previous revision: 1.6 done Checking in docs/INSTALL.txt; /usr/local/share/cvsroot/saludos/docs/INSTALL.txt,v <-- INSTALL.txt new revision: 1.2; previous revision: 1.1 done Checking in docs/NOTES.txt; /usr/local/share/cvsroot/saludos/docs/NOTES.txt,v <-- NOTES.txt new revision: 1.2; previous revision: 1.1 done Checking in docs/diseno.ppt; /usr/local/share/cvsroot/saludos/docs/diseno.ppt,v <-- diseno.ppt new revision: 1.2; previous revision: 1.1 done Checking in docs/traductor.ppt; /usr/local/share/cvsroot/saludos/docs/traductor.ppt,v <-diseno.ppt new revision: 1.2; previous revision: 1.1 done De nuevo, conviene etiquetar la rama por si en el futuro queremos pasar más revisiones de la rama al tronco, saber hasta que revisión hemos pasado: $ cvs update -r branch-ingles $ cvs tag to-trunk-ingles 2.6.3. Aplicar cambios entre ramas Aunque lo normal es aplicar cambios desde el tronco a una rama, o desde una rama al tronco. También es posible aplicar cambios entre dos ramas. La forma de proceder es similar: Nos situamos en la rama destino y ejecutamos el comando update indicando el rango de revisiones de la rama origen a aplicar sobre nuestro sandbox. Recuérdese que para referirnos a la cabeza de una rama no debemos usar HEAD, sino el nombre de la rama. Por ejemplo, si estamos situados en la rama branchingles y la corrección de errores se está haciendo en la rama branch-depuracion, podemos pasar los errores corregidos en la rama branch-depuracion a nuestra rama branch-ingles situándonos en el sandbox de la rama branch-ingles y ejecutando el comando: $ cvs update –j pre-branch-depuracion –j branch-depuracion Donde pre-branch-depuracion es la base de la rama de depuración y branchdepuracion su cabeza. Pág 74 Gestión de versiones con CVS y Subversion macprogramadores.org 2.6.4. Mezclar ficheros binarios Un problema que fácilmente puede pasar desapercibido es que al aplicar cambios entre ramas no se aplican los cambios en los ficheros binarios. En nuestro ejemplo el fichero diseno.ppt estaba marcado como binario, con lo que aunque el traductor haya modificado el fichero en la rama, cuando vayamos a editarlo desde el tronco veremos que su contenido no ha sido modificado. La única solución que tiene este handicap de CVS es copiar manualmente los ficheros binarios desde la rama origen a la rama destino. 2.7. Deshacer la aplicación de una rama En el apartado 4.2 del Tema 4 comentamos que podíamos usar el comando update para deshacer cambios hechos por un rango de revisiones. Para ello dábamos los números de revisiones hacia atrás (backward). Este rango de revisiones también puede ser el de una rama. Por ejemplo, supongamos que el consejo de dirección de nuestra empresa cambia, y pasa a dirigirlo un alto cargo de la Real Academia Española que decide que vamos a seguir distribuyendo nuestra aplicación en Español. Podemos situarnos en el sandbox del tronco y deshacer los cambios hechos por la rama con el comando: $ cvs update -j branch-ingles -j pre-branch-ingles cvs update: Updating . RCS file: /usr/local/share/cvsroot/saludos/Makefile,v retrieving revision 1.1.1.1.2.1 retrieving revision 1.1.1.1 Merging differences between 1.1.1.1.2.1 and 1.1.1.1 into Makefile RCS file: /usr/local/share/cvsroot/saludos/hola.c,v retrieving revision 1.5.2.2 retrieving revision 1.5 Merging differences between 1.5.2.2 and 1.5 into hola.c cvs update: Updating docs RCS file: /usr/local/share/cvsroot/saludos/docs/INSTALL.txt,v retrieving revision 1.1.2.1 retrieving revision 1.1 Merging differences between 1.1.2.1 and 1.1 into INSTALL.txt RCS file: /usr/local/share/cvsroot/saludos/docs/NOTES.txt,v retrieving revision 1.1.2.1 retrieving revision 1.1 Merging differences between 1.1.2.1 and 1.1 into NOTES.txt cvs update: nonmergeable file needs merge cvs update: revision 1.1 from repository is now in docs/diseno.ppt cvs update: file from working directory is now in .#diseno.ppt.1.2 C docs/diseno.ppt cvs update: scheduling docs/traductor.txt for removal Observe que se ha producido un conflicto al intentar actualizar el fichero diseno.ppt ya que es un fichero binario. Realmente hemos vuelto a obtener el fichero diseno.ppt del tronco, pero el la rama se ha quedado guardado en .#diseno.ppt.1.2, con lo que basta con borrar este fichero. Por último confirmamos los cambios con: $ cvs commit Checking in Makefile; Pág 75 Gestión de versiones con CVS y Subversion macprogramadores.org /usr/local/share/cvsroot/saludos/Makefile,v <-- Makefile new revision: 1.3; previous revision: 1.2 done Checking in hola.c; /usr/local/share/cvsroot/saludos/hola.c,v <-- hola.c new revision: 1.8; previous revision: 1.7 done Checking in docs/INSTALL.txt; /usr/local/share/cvsroot/saludos/docs/INSTALL.txt,v <-- INSTALL.txt new revision: 1.3; previous revision: 1.2 done Checking in docs/NOTES.txt; /usr/local/share/cvsroot/saludos/docs/NOTES.txt,v <-- NOTES.txt new revision: 1.3; previous revision: 1.2 done Checking in docs/diseno.ppt; /usr/local/share/cvsroot/saludos/docs/diseno.ppt,v <-- diseno.ppt new revision: 1.3; previous revision: 1.2 done Removing docs/traductor.txt; /usr/local/share/cvsroot/saludos/docs/traductor.txt,v <-traductor.txt new revision: delete; previous revision: 1.2 done 2.8. Borrar o mover una rama Al igual que cualquier otro tag, podemos borrar o mover el tag de una rama con el comando tag o rtag y las opciones –d (borrar el tag dado) o –F (mover el tag en caso de existir) respectivamente. La diferencia está en que para confirmar que sabemos lo que estamos haciendo debemos de añadir también la opción –B al comando (permitir modificar un tag de rama). En general, sólo se recomienda modificar o borrar un tag de rama cuando acabamos de crear la rama y todavía no hemos hecho ningún commit sobre la rama (no hemos creado ninguna revisión de rama). En este caso podemos querer borrar o mover la rama a otra revisión base. Ya comentamos que las ramas, una vez creadas, se quedan para siempre en el repositorio como parte de su historia. Borrar un tag de una rama ya creada no se recomienda ya que, aunque borremos el tag de rama, quedarán revisiones de rama flotando, a las cuales podremos todavía acceder con el comando update y la opción –r, pero no podremos continuar la rama o aplicar la rama a otro sitio. 2.9. Vendor branches El vendor branch es un tipo especial de rama que se usa cuando nuestro código fuente se basa en el código fuente de otro proyecto. Debido a que nuestro proyecto y el proyecto en el que nos basamos pueden evolucionar independientemente, en el futuro nos podría interesar traer los cambios del proyecto en el que nos estamos basando a nuestro repositorio. El comando import nos obliga proporcionar un vendor_tag, en el apartado 4 del Tema 3 decidimos asignarle la etiqueta ninguno, ya que no nos estábamos basando en el código fuente de nadie. Además el comando import también nos obliga a proporcionar un release_tag. El release_tag nos permite etiquetar cada versión de código fuente del vendor que estamos mezclando con nuestro código. Es muy Pág 76 Gestión de versiones con CVS y Subversion macprogramadores.org común que a la release_tag se le haga corresponder con la versión del vendor que estamos mezclando en nuestro proyecto. Por ejemplo, si estamos basando el código fuente de nuestro proyecto en la versión 0.58 del código fuente de ffmpeg (que nos hemos bajado del repositorio de ffmpeg con el comando export). La primera vez que importemos el proyecto haremos: $ cvs –d /usr/local/share/cvsroot import micodec ffmpeg ver_58 Ahora podemos empezar a modificar el tronco de nuestro proyecto, y el código fuente de ffmpeg se ve como una rama (en concreto ffmpeg siempre se asigna a la rama el número de revisión 1.1.1). Cuando en un futuro salga la versión 0.59 de ffmpeg y queramos actualizar el código de nuestro proyecto con el código de la nueva versión de ffmpeg, nos volvemos a bajar a un directorio el código fuente de ffmpeg (con el comando export), y ejecutamos un nuevo import sobre nuestro proyecto: $ cvs –d /usr/local/share/cvsroot import micodec ffmpeg ver_59 C micodec/TODO C micodec/src/ffmpeg.c 2 conflict created by this import. Use the following command to help the merge: cvs –d /usr/local/share/cvsroot checkout –j<prev_rel_tag> jver_59 micodec En concreto las reglas que aplica este comando import son: • • • Si un fichero ha cambiado en la vendor branch pero no en nuestro proyecto, CVS fija el tronco de la revisión del fichero a la nueva revisión de vendor branch (ver_59). Si el fichero ha cambiado en nuestro proyecto, pero no en el vendor, CVS no hace nada. Si el fichero ha cambiado tanto en nuestro proyecto como en el proyecto del vendor, se produce un conflicto En caso de producirse un conflicto, CVS nos sugiere hacer una variante de checkout usando la opción –j para mezclar los cambios en nuestro proyecto con los cambios del directorio donde estamos situados (la version 0.59 que hemos exportado del repositorio de ffmpeg): $ cvs –d /usr/local/share/cvsroot checkout –jver_58 -jver_59 micodec Al ejecutar este checkout se mezclaran los cambios en las líneas que no haya conflicto. Si todavía quedan conflictos, deberemos de editar los ficheros a mano y decidir qué línea es la correcta en cada conflicto. Una vez resueltos los conflictos, podemos hacer un commit de los cambios a nuestro repositorio: $ cvs commit La próxima ver que vayamos a mezclar los cambios del vendor con nuestro proyecto deberemos usar otro release_tag. Por ejemplo: $ cvs –d /usr/local/share/cvsroot import micodec ffmpeg ver_60 Pág 77 Gestión de versiones con CVS y Subversion macprogramadores.org Tema 6 CVS desde el punto de vista del administrador Sinopsis: Para acabar el estudio de CVS, en este tema se abordan los aspectos propios del administrador del repositorio. Una forma de trabajo muy usada es encargar a una persona de la creación, mantenimiento y backup del repositorio. En general esta persona es la que tiene acceso a la cuenta de root del servidor donde se guarda el repositorio. En este tema profundizaremos en cómo imponer medias de seguridad al repositorio y a los proyectos ahí guardados. También veremos cómo configurar el acceso remoto al servidor donde se encuentra el repositorio. Por último, veremos qué ficheros de configuración existen, tanto en el cliente como en el servidor, y cómo personalizar CVS usando estos ficheros. Pág 78 Gestión de versiones con CVS y Subversion 1. macprogramadores.org Seguridad en el repositorio Una de las labores del administrador es gestionar las técnicas y políticas de acceso a los proyectos del repositorio. En esta sección vamos a describir cómo funciona la seguridad en CVS. 1.1. Permisos en el sandbox Cuando hacemos export o checkout de un proyecto se crea un sandbox donde los ficheros y directorios tienen por defecto los permisos que umask asigna a los ficheros y directorios. Cuando importamos un fichero o directorio al proyecto del repositorio se pierden los permisos asignados, de forma que cuando otro usuario se baja el proyecto, se lo baja con los permisos que tenga configurados en su umask. Esto evita que un usuario pueda interferir en los permisos de los ficheros de otros usuarios. La única excepción a esta regla son los permisos de ejecución (x ) de los ficheros, los cuales se mantienen tal como estaban cuando se ejecutó el comando add sobre el fichero. Esto permite mantener el permiso de ejecución de un fichero (p.e. un script) cuando otros usuarios descargan el proyecto del repositorio a su sandbox. Una vez que el fichero ha sido añadido al repositorio (con el comando add) y subido al proyecto del repositorio (con el comando commit ), los cambios que hagamos en el permiso de ejecución de los ficheros del sandbox no afectan al fichero en el repositorio. La técnica que usa CVS para almacenar el permiso de ejecución de un fichero es activar el permiso de ejecución en el fichero del proyecto del repositorio, con lo que si queremos activar el permiso de ejecución de un fichero que ya existía en el proyecto del repositorio podemos activar el bit de ejecución directamente en el fichero del repositorio. Después no basta con hacer un update en el sandbox para que el fichero adquiera el permiso de ejecución, sino que debemos borrar el fichero en el sandbox, y ejecutar update para recuperar el fichero del proyecto del repositorio. Por último comentar que un usuario puede hacer que todos los ficheros del proyecto del repositorio se descarguen a su sandbox con sólo el bit the lectura definiendo la variable de entorno: $ export CVSREAD=1 1.2. Permisos en el repositorio Un problema que preocupa al administrador de un repositorio es qué permisos asignar a los ficheros y directorios dentro del directorio del repositorio. En consecuencia, la gestión de la seguridad en el repositorio es más compleja, porque depende de los permisos del sistema de ficheros donde está alojado el repositorio. En principio los ficheros y directorios del repositorio también se crean de acuerdo al umask del último usuario que escribió cada fichero del repositorio. Además los ficheros del repositorio tendrán como owner el último usuario que escribió en el fichero, y como group el grupo del usuario que escribió en el fichero. Si queremos mejorar la seguridad de los proyectos del repositorio se recomienda: Pág 79 Gestión de versiones con CVS y Subversion macprogramadores.org 1. Crear un grupo por cada proyecto y añadir a ese grupo sólo a los usuarios que deban tener acceso a cada proyecto del repositorio. 2. Cambiar el grupo de los ficheros del proyecto al grupo que hemos creado para ese proyecto. 3. Activar el SGID sobre el directorio del proyecto para que los nuevos ficheros que se creen hereden estos permisos. Por ejemplo, supongamos que vamos a crear en el repositorio el proyecto mate donde vamos a almacenar scripts que realizan cálculos matemáticos, y queremos crear el grupo mate para que sólo los usuarios de este grupo puedan acceder al proyecto del repositorio. Lo primero que empezamos haciendo es importar el proyecto: $ N N N N N cvs -d /usr/local/share/cvsroot import mate nnguno ver_inicial mate/resta.sh mate/multiplica.sh mate/LEEME.txt mate/divide.sh mate/suma.sh Si ahora nos vamos al directorio del proyecto del repositorio vemos que el owner y group son los del usuario que ha creado el proyecto: $ cd /usr/local/share/cvsroot/mate $ umask 0022 $ ls -la drwxrwxr-x 2 flh admin 176 2007-07-26 drwxr-xr-x 8 flh admin 216 2007-07-26 -r--r--r-- 1 flh admin 407 2007-07-26 -r-xr-xr-x 1 flh admin 398 2007-07-26 -r-xr-xr-x 1 flh admin 402 2007-07-26 -r-xr-xr-x 1 flh admin 397 2007-07-26 -r-xr-xr-x 1 flh admin 396 2007-07-26 21:04 21:04 21:26 21:04 21:04 21:04 21:04 ./ ../ LEEME.txt,v divide.sh,v* multiplica.sh,v* resta.sh,v* suma.sh,v* Los ficheros que se han creado con el permiso de ejecución, se han creado así porque se han importado con este permiso activado, pero debido a que el umask de flh era 0222, tanto los usuarios del grupo admin como los que no lo sean tienen permiso de lectura. El lector perspicaz habrá observado que flh tiene permiso de lectura sobre los ficheros del proyecto del repositorio, y no de lectura y escritura a pesar de tener un 0 en su umask: El permiso de ejecución se activa cuando está activado al importar los ficheros (su valor no depende del umask ). El permiso de escritura no se activa nunca, sino que cada vez que CVS interactúa con el proyecto del repositorio activa el permiso de escritura en el fichero, lo modifica, y vuelve a desactivarlo. De esta forma se intenta evitar que los usuarios intenten modificar directamente los ficheros del proyecto del repositorio. Los ficheros del repositorio siempre se deben modificar a través de un sandbox. En el apartado 4 profundizaremos en este aspecto de CVS. Suponiendo que ya hemos creado el grupo mate donde estará flh y los demás miembros de este proyecto, lo siguiente es cambiar los permisos del proyecto para que sólo los miembros del grupo mate puedan leer en el directorio del proyecto en el repositorio. $ chmod o-rwx * . Pág 80 Gestión de versiones con CVS y Subversion $ chgrp mate * . $ ls -la drwxrwx--- 2 flh drwxr-xr-x 8 flh -r--r----- 1 flh -r-xr-x--- 1 flh -r-xr-x--- 1 flh -r-xr-x--- 1 flh -r-xr-x--- 1 flh mate admin mate mate mate mate mate 208 216 407 401 405 400 399 macprogramadores.org 2007-07-26 2007-07-26 2007-07-26 2007-07-26 2007-07-26 2007-07-26 2007-07-26 21:26 21:26 21:26 21:26 21:26 21:26 21:26 ./ ../ LEEME.txt,v divide.sh,v* multiplica.sh,v* resta.sh,v* suma.sh,v* En este momento todos los ficheros pertenecen al grupo mate. Los usuarios que no sean del grupo mate no tienen acceso a estos ficheros. El problema es que ahora si flh hiciese un commit sobre, por ejemplo, el fichero suma.sh, los permisos cambiarían a: $ ls -la drwxrwx--drwxr-xr-x -r--r-----r-xr-x---r-xr-x---r-xr-x---r-xr-x--- 2 9 1 1 1 1 1 flh flh flh flh flh flh flh mate admin mate mate mate mate admin 208 248 407 401 405 400 476 2007-07-26 2007-07-26 2007-07-26 2007-07-26 2007-07-26 2007-07-26 2007-07-26 21:39 21:38 21:26 21:26 21:26 21:26 21:39 ./ ../ LEEME.txt,v divide.sh,v* multiplica.sh,v* resta.sh,v* suma.sh,v* Para evitar este problema, la pieza que nos falta es activar el SGID sobre el directorio del proyecto del repositorio. Este permiso hace que los ficheros y subdirectorios contenidos en el directorio se creen con los permisos del directorio: $ sudo chmod ug+s $ ls -la drwsrws--- 2 flh drwxr-xr-x 8 flh -r--r----- 1 flh -r-xr-x--- 1 flh -r-xr-x--- 1 flh -r-xr-x--- 1 flh -r-xr-x--- 1 flh . mate admin mate mate mate mate mate 208 216 407 401 405 400 400 2007-07-26 2007-07-26 2007-07-26 2007-07-26 2007-07-26 2007-07-26 2007-07-26 21:56 21:56 21:56 21:56 21:56 21:56 21:56 ./ ../ LEEME.txt,v divide.sh,v* multiplica.sh,v* resta.sh,v* suma.sh,v* Ahora, los usuarios del grupo mate pueden desde su sandbox crear y modificar ficheros y subdirectorios en el proyecto sin que los permisos, owner y group del proyecto del repositorio cambien. 1.3. El repositorio y el directorio CVSROOT En este momento hemos solucionado el problema de la seguridad de un proyecto del repositorio, pero conviene también proteger todo el repositorio, para lo cual se recomienda crear otro grupo (normalmente llamado cvs) en el que aparecen los usuarios que tienen permiso para trabajar sobre el CVS. Este grupo es el grupo que se asigna tanto al directorio del repositorio, como al subdirectorio CVSROOT (donde se almacenan metadatos de los distintos proyectos). En concreto, si esta es la estructura de permisos de nuestro repositorio: $ cd /usr/local/share/cvsroot $ ls -la drwxr-xr-x 8 flh admin 216 2007-07-26 21:56 ./ drwxr-xr-x 13 flh admin 352 2007-07-15 10:35 ../ Pág 81 Gestión de versiones con CVS y Subversion drwxr-xr-x drwsrws--drwxr-xr-x 3 flh 2 flh 4 flh macprogramadores.org admin 1464 2007-06-30 08:08 CVSROOT/ mate 208 2007-07-26 22:00 mate/ admin 152 2007-07-24 21:27 saludos/ Podemos hacer sobre el directorio del repositorio: $ chgrp cvs . $ chgrp -R cvs saludos $ chmod o-rwx . $ chmod -R o-rwx saludos $ chmod ug+s . saludos $ chmod g+w saludos $ ls -la drwsr-s--8 flh cvs 216 2007-07-26 21:56 drwxr-xr-x 13 flh admin 352 2007-07-15 10:35 drwxr-xr-x 3 flh admin 1464 2007-06-30 08:08 drwsrws--2 flh mate 208 2007-07-26 22:00 drwsrws--4 flh cvs 152 2007-07-26 22:17 ./ ../ CVSROOT/ mate/ saludos/ En este momento, sólo los miembros del grupo cvs pueden acceder y modificar el proyecto de repositorio saludos, y sólo los miembros que además de al grupo cvs pertenezcan al grupo mate podrán acceder o modificar el proyecto de repositorio mate. Ahora nos queda por proteger el subdirectorio CVSROOT. Este directorio es el más difícil de proteger, porque si un usuario distinto del administrador del repositorio (flh en este ejemplo) consigue escribir en sus ficheros, puede modificar algún script para escalar sus privilegios. Sin embargo los usuarios necesitan acceso al directorio CVSROOT para poder operar contra el repositorio. La forma correcta de protegerlo es: 1. Quitar a los usuarios que no sean del grupo cualquier permiso sobre estos ficheros. 2. Asignar al owner y al group permiso de lectura sobre todos los ficheros. 3. Asignar al owner y al group permiso de escritura sobre los ficheros history y val-tags. En el primero se necesita el permiso de escritura para poder guardar logs, en el segundo se necesita permiso de escritura para poder crear y modificar tags. Luego haríamos: $ chgrp –R cvs CVSROOT $ chmod 770 CVSROOT $ chmod ug+s CVSROOT $ cd CVSROOT $ chmod 440 * $ chmod ug+w history val-tags $ cd .. $ ls -la drwsr-s--8 flh cvs 216 drwxr-xr-x 13 flh admin 352 drwsrws--3 flh cvs 1512 drwsrws--2 flh mate 208 drwsrws--4 flh cvs 152 2007-07-26 2007-07-28 2007-07-28 2007-07-26 2007-07-28 21:56 19:27 15:11 22:00 19:00 ./ ../ CVSROOT/ mate/ saludos/ En este momento sólo los miembros del grupo cvs pueden leer los ficheros del subdirectorio CVSROOT. Además, sólo estos miembros pueden escribir en los ficheros history y val-tags. El SGID sobre el subdirectorio CVSROOT hace cuando los Pág 82 Gestión de versiones con CVS y Subversion macprogramadores.org usuarios modifiquen los ficheros de este subdirectorio, se mantengan los permisos del subdirectorio en sus ficheros contenidos. Tenga en cuenta que los únicos ficheros de este subdirectorio que van a modificar son history y val-tags. 2. Acceso remoto al repositorio con pserver En el apartado 3 del Tema 3 vimos cómo acceder a un repositorio remoto con el método ext, que es el método más usado y seguro para acceder a un repositorio. En este apartado vamos a ver cómo acceder a un repositorio remoto con el método pserver, que aunque en condiciones normales es menos seguro, sabiendo configurarlo es muy útil cuando queremos crear una cuenta de acceso restringido, como por ejemplo una cuenta anónima que sólo permita leer el repositorio. 2.1. Activar el servicio Cuando un cliente remoto usa el método pserver para conectarse a un repositorio, el cliente se está conectando al puerto 2401 (492 para lo que les gusten este tipo de nemónicos) del servidor donde está alojado el repositorio. En la máquina servidor no existe un proceso esperando al cliente, sino que se usa el programa inetd para lanzar el servidor de CVS cuando se recibe una petición de conexión. Para que este mecanismo funcione debemos de asegurarnos de que el fichero /etc/services tiene una entrada de la forma: cvspserver 2401/tcp #CVS network server Y ponerla en caso de no existir. Lo otro que tenemos que hacer es modificar el fichero /etc/inetd.conf para que tenga una línea de la forma: cvspserver stream tcp nowait root /usr/bin/cvs cvs --allow-root=/usr/local/share/cvsroot pserver O en caso de estar usando tcpwrappers: cvspserver stream tcp nowait root /usr/sbin/tcpd /usr/bin/cvs --allow-root=/usr/local/share/cvsroot pserver Por último reiniciamos inetd para que vuelva a leer su fichero de configuración. 2.2. El fichero passwd Dentro del subdirectorio CVSROOT del directorio del repositorio debemos crear un fichero con el nombre passwd (por defecto no existe) dentro del cual se almacenarán los usuarios y password de CVS en líneas de la forma: cvs_username:password:system_username Donde cvs_username es el nombre que enviamos a CVS para identificarnos y system_username es el usuario del sistema bajo el que corre el programa en el servidor. Si ambos nombres son iguales se puede omitir el system_username, y por defecto será el cvs_username. El cvs_username es el que se muestra en los Pág 83 Gestión de versiones con CVS y Subversion macprogramadores.org históricos que almacena CVS para guardar información de log. El system_username debe existir en los ficheros de password del repositorio: /etc/password en el caso de muchos sistemas UNIX, o en NetInfo en el caso de Mac OS X. Un ejemplo de fichero CVSROOT/password sería: $ cat CVSROOT/passwd flh:dsVu7MU7smXoY chris:dsYmKkcaHnecU:cvsuser peter:ds/v9F0Jd1s.E:cvsuser El usuario flh se ejecuta con los permisos de la cuenta de flh en el sistema. Los usuarios chris y peter se ejecutan ambos con los permisos del usuario cvsuser en el sistema, pero los log se generan de acuerdo a sus respectivos cvs_username. Para generar los password se utiliza la función crypt() del sistema, que es la misma que se usa para generar los password de /etc/password (en otros sistemas los password se almacenan en /etc/shadow). Nosotros podemos generar estos password con el comando perl –e 'crypt "password", "salt";', donde password es el texto plano que queremos usar como password y salt son dos caracteres aleatorios que se usan para incrementar la seguridad. Por ejemplo: $ perl -e 'print crypt "sesamo", "ds";' dspis3aAFOviY 2.3. Logarse en CVS Si la cuenta del fichero CVSROOT/passwd requiere password para un usuario, éste tendrá que logarse en CVS, con el comando login, antes de poder ejecutar cualquier otro comando CVS. Como cadena de conexión para pserver se usa una cadena de la forma: :pserver:[[user][:password]@][hostname:[port]]/path Donde user es el cvs_username, si no se proporciona user se usa el nombre de usuario de la máquina cliente. El campo password es el password plano que almacenamos en CVSROOT/passwd. Existe un mecanismo de fallback por el que si el password proporcionado falla se intenta comprobar el password del sistema, y si éste coincide, se acepta al usuario. Esto permite al administrador no tener que estar creando passwords en el fichero CVSROOT/passwd, sino que los usuarios que tengan cuenta en el sistema puedan usar su password de sistema. El campo hostname es el nombre de la máquina donde está instalado el repositorio de CVS, el campo port nos permite usar un puerto distinto al 2401, y el path es el path del directorio del repositorio en el servidor. Por ejemplo, para logarnos en la máquina localhost con la cuenta de usuario de flh haríamos: $ cvs -d :pserver:flh@localhost/usr/local/share/cvsroot login Logging in to :pserver:flh@localhost:2401/usr/local/share/cvsroot CVS password:sesamo Una vez que nos hemos logado con éxito, en la máquina cliente se almacena el password en el fichero .cvspass del directorio home, de esta forma no es necesario volver a especificar el password: Pág 84 Gestión de versiones con CVS y Subversion macprogramadores.org $ cat ~/.cvspass /1 :pserver:[email protected] /cvsroot/mpeg4ip A /1 :pserver:[email protected] /cvsroot/vitooki A /1 :pserver:flh@localhost /usr/local/share/cvsroot AZdZy%0 Lo que sí es necesario es volver a especificar la cadena de conexión si vamos a hacer un checkout. Por ejemplo: $ cvs -d :pserver:flh@localhost/usr/local/share/cvsroot checkout saludos cvs checkout: Updating saludos U saludos/Makefile U saludos/hola.c cvs checkout: Updating saludos/docs U saludos/docs/INSTALL.txt U saludos/docs/NOTES.txt U saludos/docs/diseno.ppt A partir de este momento la cadena de conexión se almacena en los ficheros de metadatos del sandbox, y tampoco es necesario volver a especificarla. El fichero .cvspass es un claro riesgo de seguridad, ya que se queda almacenado en nuestro disco duro indefinidamente. Podemos usar el comando logout para liberar la entrada de este fichero. El comando logout deberá ejecutarse desde el sandbox, o bien especificar la cadena de conexión. $ cd saludos $ cvs logout Logging out of :pserver:flh@localhost:2401/usr/local/share/cvsroot Por último comentar que podemos pedir que la cadena de conexión y password se almacenen en un fichero distinto a .cvspass indicando su ruta en la variable de entorno CVSPASSFILE del cliente. 2.4. Los ficheros readers y writers Podemos crear los ficheros readers y writers en el subdirectorio CVSROOT del repositorio para controlar qué usuarios tienen permisos de lectura y escritura en el repositorio. Estos ficheros sólo afectan a los usuarios que usan el modo pserver. Los permisos de los usuarios que usan el modo ext se controla por los permisos del sistema de ficheros. En concreto, los ficheros readers y writers pueden tener una línea por cada usuario y la política de acceso sería: • • • • Si un usuario aparece en readers, el usuario sólo tiene acceso de lectura al repositorio. Si el fichero writers no existe, todos los usuarios que no aparezcan en readers tienen permiso de lectura y escritura. Si el fichero writers existe, sólo los usuarios que aparezcan en este fichero tienen permiso de escritura en el repositorio. Si un usuario aparece en el fichero readers y writers, CVS sigue la opción más conservadora y sólo le da acceso de lectura. Pág 85 Gestión de versiones con CVS y Subversion macprogramadores.org 2.5. Crear una cuenta anónima Muchos repositorios CVS (especialmente los destinados a software abierto) tienen una cuenta anónima (con el usuario anonymous), que permite a todo el mundo poder bajarse el contenido del repositorio sin necesidad de tener un password. Para crear una cuenta de este tipo en CVS lo que se hace es crear la cuenta anonymous en el fichero CVSROOT/passwd sin password de la forma: $ cat CVSROOT/passwd anonymous: flh:dsVu7MU7smXoY chris:dsYmKkcaHnecU:cvsuser peter:ds/v9F0Jd1s.E:cvsuser Obsérvese que hemos creado la cuenta anonymous sin password, lo cual hace que se active el mecanismo de fallback y se consulte el password del sistema. Como la cuenta anonymous tampoco tiene cvs_username, deberá existir un usuario anonymous en el sistema. En nuestro caso podemos crear este usuario en 1 /etc/passowd de la forma : anonymous:!:1745:100:Usuario anonimo de CVS :/usr/local/share/cvsroot:/bin/false La ! hace que el usuario no tenga password. Para evitar que alguien se conecte a nuestro sistema mediante una shell, hemos reemplazado la shell por /bin/false. Ahora el mecanismo de fallback consultará la cuenta de sistema del usuario anonymous, y al ver que no tiene password nos permitirá entrar en CVS con los permisos efectivos del usuario anónimo. Recuérdese que en el apartado 1.2 cambiamos los permisos del repositorio para que sólo los usuarios del grupo cvs pudieran acceder a él. Con lo cual deberemos añadir al usuario anonymous al grupo cvs. En este momento, si tenemos creada la cuenta anonymous sin password, todo el mundo puede acceder al repositorio y modificarlo usando esta cuenta. Para darles acceso de sólo lectura, lo único que tenemos que hacer es declarar al usuario anonymous en el fichero readers: $ cat CVSROOT/readers anonymous Ahora nos podemos logar con la cuenta anónima en el servidor de la forma: $ cvs -d :pserver:anonymous@localhost/usr/local/share/cvsroot login Logging in to :pserver:anonymous@localhost:2401/usr/local/share/cvsroot CVS password: Cuando se nos pida el password, simplemente pulsamos intro. Y ya podemos bajarnos proyectos del repositorio con export o con checkout: 1 En Mac OS X cree una cuenta anonymous sin password usando NetInfo. Pág 86 Gestión de versiones con CVS y Subversion macprogramadores.org $ cvs -d :pserver:anonymous@localhost/usr/local/share/cvsroot checkout saludos cvs checkout: Updating saludos U saludos/Makefile U saludos/hola.c cvs checkout: Updating saludos/docs U saludos/docs/INSTALL.txt U saludos/docs/NOTES.txt U saludos/docs/diseno.ppt Lo que no podremos hacer con la cuenta anonymous es modificar el repositorio: $ cvs commit cvs [server aborted]: "commit" requires write access to the repository 2.6. Consideraciones de seguridad Los ficheros CVSROOT/passwd, CVSROOT/readers y CVS/writers son ficheros que cualquier usuario que tenga acceso al repositorio podría usar para escalar sus privilegios. Debido a que en el modo pserver CVS es ejecutado en el servidor por el usuario root (ya que así lo configuramos en el fichero inetd.conf), éste debe de ser el único usuario que tenga permiso de lectura o escritura sobre estos ficheros. Además en el modo pserver el password viaja por la red sin encriptar, con lo que se recomienda que el password que usen los usuarios de CVS para identificarse sea distinto al password que usan los usuarios para logarse en el servidor. Sin embargo el mecanismo de fallback hace que si un usuario falla al dar su password de CVS, se intente validad su password de sistema. Podemos desactivar este mecanismo de fallback asignando la propiedad SystemAuth a no en el fichero CVSROOT/config. Este fichero no se debe de modificar directamente, sino que en el apartado 4 veremos cómo se modifica este fichero. El modo pserver es útil porque es la única forma de crear una cuenta anónima de sólo lectura, y también es útil porque nos permite crear cuentas que sólo pueden acceder a CVS (y no a todo el sistema), pero tiene el inconveniente de que el password de estas cuentas nos lo pueden interceptar por la red. Si el usuario tiene una cuenta en el sistema, resulta mucho más seguro usar el modo ext con ssh. En caso de que exista un firewall entre el servidor y los clientes, el modo pserver requiere que esté abierto el puerto 2401 de entrada al servidor, mientras que el modo ext con ssh requiere que esté abierto el puerto 22 de entrada al servidor. 3. Configuración del cliente CVS proporciona varios mecanismos para que cada usuario pueda personalizar el comportamiento de su CVS. Estos mecanismos los vamos a estudiar en tres grupos: (1) ficheros de configuración en el sandbox, (2) ficheros de configuración en el directorio home del usuario, y (3) variables de entorno en la sesión del usuario. Pág 87 Gestión de versiones con CVS y Subversion 3.1. macprogramadores.org Ficheros de configuración en el sandbox En el sandbox se puede crear básicamente un fichero que es el fichero .cvsignore. Este fichero contiene los ficheros y directorios del sandbox que queremos que CVS ignore cuando comprueba lo ficheros que están actualizados con el sandbox. Muchos entornos de desarrollo, como por ejemplo NetBeans, generan ficheros de proyecto. Como regla general, estos ficheros de proyecto no se deben de guardar en el repositorio, ya que el contenido de estos ficheros de proyecto (p.e. rutas a ficheros) dependen del cliente, y además no es bueno obligar a todos los clientes a utilizar una determinada herramienta de desarrollo. Por ello, estos ficheros de proyecto suelen quedar en el sandbox y cuando ejecutamos el comando update obtenemos mensajes como: $ cvs update ? build ? dist ? nbproject ? build.xml cvs update: Updating . cvs update: Updating docs Donde la ? nos indica que el fichero build.xml y los subdirectorios build, dist, y nbproject no están en el repositorio. Podemos pedir a CVS que ignore estos ficheros y directorios del sandbox creando el fichero: $ cat .cvsignore .cvsignore build dist nbproject build.xml Sí, tenemos que pedir a .cvsignore que se ignore a si mismo. El fichero también puede contener patrones (p.e. *.class). Ahora al ejecutar el comando update desaparecerán estos ficheros: $ cvs update cvs update: Updating . cvs update: Updating docs 3.2. Ficheros de configuración en el directorio home CVS lee varios ficheros en el directorio home del usuario: .cvsrc Contiene comandos y opciones que se usarán por defecto para cada comando. En el apartado 11 del Tema 4 explicamos el uso de este fichero. .cvsignore Contiene un listado de ficheros que CVS debe ignorar, y su formato es similar al explicado en el apartado 3.1. Este fichero no está creado por defecto. A diferencia del fichero .cvsignore en el sandbox, el fichero .cvsignore en el directorio home afecta a todos los sandbox de este usuario. Pág 88 Gestión de versiones con CVS y Subversion macprogramadores.org .cvswrappers Contiene una lista de patrones y la forma en que se deben almacenar (texto o binario) los ficheros que cumplan este patrón. Su formato es similar al explicado en el apartado 10 del Tema 4. .cvspass Usado por el modo pserver para almacenar la cadenas de conexión y passwords de proyectos 3.3. Variables de entorno Por último CVS también lee las variables de entorno del usuario descritas en la siguiente lista: CVS_CLIENT_LOG Usada para depuración en modo cliente/servidor. La variable debe de contener el path de un fichero. En este caso todo lo que se envía al servidor se almacena en el fichero fichero.in, y todo lo que recibe el cliente se almacena en el fichero fichero.out. CVSIGNORE Una lista separada por espacios de nombres de ficheros a ignorar. Su formato es similar al del fichero .cvsignore. CVSEDITOR o EDITOR o VISUAL Esta lista de variables se comprueba en orden hasta que alguna de ellas exista. En cuyo caso su valor será el nombre de la herramienta que se usará para editar los mensajes de log de CVS. Véase apartado 4 del Tema 3. CVS_PASSFILE Usada para indicar un fichero alternativo a .cvspass para almacenar las cadenas de conexión y password del modo pserver. CVSREAD Si tiene asignado el valor 1, CVS intenta hacer un checkout del sandbox en modo de sólo lectura. CVSROOT Si esta variable de entorno existe, contiene la cadena de conexión al repositorio, con lo que no es necesario usar la opción –d del comando cvs para indicar la cadena de conexión1. 4. Configuración del servidor El subdirectorio CVSROOT contiene una serie de ficheros de configuración del repositorio. En este apartado vamos a ver qué contienen estos ficheros, y cómo modificarlos. 4.1. Acceso a los ficheros de CVSROOT Los ficheros de configuración almacenados en CVSROOT no se deben de modificar directamente, sino que debemos bajárnoslos a un sandbox, modificarlos en el sandbox y hacer un commit de los cambios. La excepción a esta regla son los ficheros de configuración passwd, readers y writers. Debido a que estos ficheros son muy sensibles, deben de tener permiso de lectura sólo por la cuenta root, y no se bajan a un sandbox para modificarlos. Los demás ficheros (los que sí que se modifican a través de un sandbox) tienen dos versiones en el subdirectorio CVSROOT: Una con el nombre del fichero de configuración donde se almacena la última versión del fichero, y otra con el nombre del fichero de configuración con la extensión ,v donde se almacena información de versionado. 1 Ojo con no confundir la variable de entorno CVSROOT con el subdirectorio CVSROOT. Pág 89 Gestión de versiones con CVS y Subversion macprogramadores.org Supongamos que queremos modificar el fichero CVSROOT/config, que en el apartado 2.6 comentamos que podíamos modificar para mejorar la seguridad del modo pserver. Para ello empezamos bajándonos el repositorio: $ cvs -d /usr/local/share/cvsroot checkout CVSROOT cvs checkout: Updating CVSROOT U CVSROOT/checkoutlist U CVSROOT/commitinfo U CVSROOT/config U CVSROOT/cvswrappers U CVSROOT/loginfo U CVSROOT/modules U CVSROOT/notify U CVSROOT/postadmin U CVSROOT/postproxy U CVSROOT/posttag U CVSROOT/postwatch U CVSROOT/preproxy U CVSROOT/rcsinfo U CVSROOT/taginfo U CVSROOT/verifymsg Vemos que los ficheros passwd, readers y writers no se bajan, ya que por seguridad están fuera de está técnica de gestión. Ahora podemos editar el fichero config y activar la propiedad SystemAuth a no, tal como comentamos en el apartado 2.6. Por último, para subir el fichero hacemos un commit como normalmente: $ cvs commit /usr/local/share/cvsroot/CVSROOT/config,v <-- config new revision: 1.2; previous revision: 1.1 done cvs commit: Rebuilding administrative file database El mensaje Rebuilding administrative file database sólo se produce cuando estamos modificando el subdirectorio CVSROOT. 4.2. Ficheros de configuración En este apartado vamos a resumir los ficheros de configuración más importantes, y cuál es su utilidad: config contiene pares key= value con múltiples opciones de configuración. Conviene que el lector edite este fichero para hacerse una idea de su contenido. cvswrappers contiene una lista de patrones de ficheros que ayudan a CVS a saber cuándo tratar a un fichero como texto, y cuándo como un fichero binario. commitinfo permite ejecutar un script antes de hacer commit de un fichero. Un uso típico es comprobar que los ficheros a guardar en el repositorio cumplan con ciertas reglas de codificación. Si el script acaba con un código de terminación distinto de cero, el commit no se acepta. loginfo define un script a ejecutar después de haber hecho commit de un fichero. Su principal finalidad es hacer log del cambio. rcsinfo permite definir la plantilla de un formulario que se muestra al usuario cada vez que éste va a hacer commit de un fichero. Pág 90 Gestión de versiones con CVS y Subversion macprogramadores.org taginfo permite almacenar un script que se ejecuta antes de crear un tag. Su principal uso es comprobar que el tag cumpla con las reglas de nombres de tag del proyecto. Si el script acaba con un código de terminación distinto de cero, el tag no se acepta. verifymsg permite comprobar el contenido de los log de commit que emiten los usuarios. Este script suele comprobar que el usuario haya rellenado el mensaje de acuerdo a la política del proyecto. Muchas veces se usa junto con rcsinfo para comprobar que todos los campos de la plantilla dada en rcsinfo han sido rellenados. 4.3. Hook scripts Los ficheros de configuración del apartado anterior nos permiten crear varios hook scripts que se ejecutan en el servidor donde está alojado el repositorio cuando ocurre una determinada acción. El ejemplo más típico de hook script es el fichero commitinfo, el cual se ejecuta antes de aceptar el commit de un fichero. Vamos a crear un script de ejemplo que valida el que el fichero esté indentado de acuerdo a las reglas de indentado del proyecto. El comando indent permite indentar un código fuente C de acuerdo a varias reglas de indentado. Si le pasamos un fichero y la opción –gnu, indenta el fichero de acuerdo a las reglas de indentado de GNU. Por ejemplo: $ cat hola.c #include <stdio.h> int main() { printf("Buenos dias, y bienvenido al mundo controlado en CVS\n"); printf("Este fichero esta mas completo que antes\n"); return 0; } $ cat hola.c | indent -gnu #include <stdio.h> int main () { printf ("Buenos dias, y bienvenido al mundo controlado en CVS\n"); printf ("Este fichero esta mas completo que antes\n"); return 0; } El Listado 6.1 muestra un pequeño script que sirve para comprobar si los ficheros que le pasamos como parámetro están indentados de acuerdo a una determinada regla de indentado. #!/bin/bash opcion=$1 shift dir=$1 shift # Por cada fichero recibido while test "$1" != "" do case $1 in *.c|*.h) fichero_tmp="/tmp/id.$$" Pág 91 Gestión de versiones con CVS y Subversion macprogramadores.org cat $1 | indent $opcion > $fichero_tmp diff $1 $fichero_tmp >/dev/null 2>/dev/null if [ $? -ne 0 ]; then exit 1 fi;; esac shift done exit 0 Listado 6.1: Script indent-tester.sh Si el código de terminación es 0 es que están bien indentados. Si el código de terminación es 1 es que están mal indentados. Para instalar este script en commitinfo, lo que tenemos que hacer es bajarnos el subdirectorio CVSROOT a un sandbox, introducir una línea de la forma: ^saludos /usr/local/share/cvsroot/CVSROOT/indent-tester.sh –gnu Y volver a subirlo al repositorio. La primera parte de esta línea es el patrón que debe de cumplir el fichero empezando por el directorio del repositorio. En este ejemplo el patrón sólo afecta a los ficheros del proyecto saludos. La segunda parte es el script a ejecutar. Cuando se ejecute el script indent-tester.sh se le pasará como argumento la opción –gnu, después CVS pasa el directorio del proyecto del repositorio, y después los nombres de todos los ficheros del sandbox de los que se intenta hacer commit. Por esta razón el script indent-tester.sh debe estar preparado para recibir varios nombres de fichero, y comprobar todos ellos. El script solo comprueba que el fichero esté bien indentado en los ficheros con extensión .c o .h. Si ahora el usuario intenta hacer commit de un fichero que no está correctamente indentado el commit falla: $ cat hola.c #include <stdio.h> int main() { printf("Buenos dias, y bienvenido al mundo controlado en CVS\n"); printf("Este fichero esta mas completo que antes\n"); return 0; } $ cvs commit hola.c cvs commit: Pre-commit check failed cvs [commit aborted]: correct above errors first! Una vez que el usuario lo indenta el commit se acepta: $ indent -gnu hola.c $ cvs commit hola.c Checking in hola.c; /usr/local/share/cvsroot/saludos/hola.c,v new revision: 1.9; previous revision: 1.8 done Pág 92 <-- hola.c Gestión de versiones con CVS y Subversion macprogramadores.org Tema 7 Guía rápida de Subversion Sinopsis: Este tema pretende resumir los principales aspectos necesarios para el manejo de Subversion. Si no tiene mucho tiempo para aprender a utilizar Subversion, quizá le sea suficiente con leer este tema. En los siguientes temas se estudiará con más detalle las características y funcionalidades que Subversion ofrece. Pág 93 Gestión de versiones con CVS y Subversion 1. macprogramadores.org Características de Subversion Subversion es un software de licencia de código fuente abierto. El texto de esta licencia no es GPL, MIT, ni otro tipo de licencia conocida, sino un tipo de licencia propia llamada Licencia Subversion. El software está siendo desarrollado en su mayoría por miembros de la empresa CollabNet, una empresa especializada en ingeniería del software. SVN fue diseñado por expertos en ingeniería del software para mejorar la forma de trabajar de CVS, pero con la idea de que la migración desde CVS fuese lo más sencilla posible. Con este fin decidieron mantener los mismos nombres de comandos de CVS, siempre que no existiera una razón de peso para cambiar el nombre. Esto permite que un usuario acostumbrado a usar CVS encuentre bastante sencilla la migración a SVN. Al igual que la mayoría de las herramientas de gestión de versiones, Subversion sigue el modelo cliente servidor donde el repositorio se instala en una máquina que actúa como servidor y los clientes se bajan un proyecto del repositorio a un directorio de su máquina llamado working copy. Los programadores modifican los ficheros en su working copy y luego envían los cambios al proyecto del repositorio. Una característica de Subversion que le diferencia de la competencia es que es muy flexible a la hora de cambiar la organización de los ficheros y directorios del proyecto. Además de permitirnos mover, copiar y renombrar ficheros del proyecto, nos permite guardar un log de los cambios. Esta característica podría no parecer gran cosa si no se compara Subversion con otros gestores de versiones como CVS, donde no se pueden mover ficheros del proyecto sin partir la historia del fichero, y peor aun, CVS no permite mover o borrar subdirectorios del proyecto. En Microsoft Visual SourceSafe, aunque no es tan malo en este aspecto como en CVS, resulta mucho más difícil modificar la estructura de los ficheros del proyecto que con Subversion. Como se explicó en los apartados 3 y 4 del Tema 2, la mayoría de los gestores de versiones introducen el concepto de tags (etiquetas) y branches (ramas). Subversion no incluye estos conceptos explícitamente, pero proporciona su funcionalidad mediante copias ligeras. Para poner un tag a los ficheros del proyecto lo que proponen es hacer una copia ligera del proyecto en un subdirectorio tags. De igual forma, para crear una rama proponen crear una copia ligera del proyecto en un subdirectorio branches. En principal inconveniente que tiene la forma en que Subversion soluciona los tags es que nadie nos garantiza que alguien no modifique el contenido de los subdirectorios del directorio tags, convirtiendo estos tags en branches. Los ficheros de texto son más fáciles de gestionar por un gestor de versiones, porque el gestor de versiones almacena los cambios en las líneas de texto. Sin embargo es muy útil que un gestor de versiones también permita almacenar ficheros binarios como, por ejemplo, documentación, imágenes, y otros recursos. Subversion, al igual que la mayoría de los gestores de versiones también permite almacenar ficheros binarios. Las revisiones de los ficheros binarios, a diferencia de los ficheros de texto, no se almacenan como diferencias entre líneas, sino que se almacenan nuevas copias del fichero. En concreto, en el apartado 7 del Tema 8 veremos que Subversion es capaz de detectar automáticamente el tipo del fichero y si se trata de un fichero binario tratarlo como tal. Otra característica de Subversion es que (al igual que Arch y a diferencia de CVS) permite almacenar enlaces simbólicos. Al almacenar enlaces simbólicos se almacena Pág 94 Gestión de versiones con CVS y Subversion macprogramadores.org la ruta apuntada por el enlace simbólico en el repositorio y se descarga en las working copies de los usuarios. Sin embargo, almacenar enlaces simbólicos presenta dos problemas: (1) Al descargar el enlace simbólico a la working copy de otro usuario este enlace simbólico puede estar apuntando a una ruta que no exista en esa máquina, y (2) y los enlaces simbólicos no funcionan en algunos sistemas como Microsoft Windows. Para almacenar el repositorio Subversion utiliza dos mecanismos de almacenamiento alternativos. El mecanismo de almacenamiento a usar se decide durante la creación del repositorio. El primer mecanismo de almacenamiento es la Berkeley DB que existe desde la versión 1.0. En la versión 1.1 se introdujo otro mecanismo de almacenamiento llamado FSFS (File System based File System). En principio se recomienda usar el segundo mecanismo de almacenamiento ya que si se corrompe la base de datos del repositorio no se pierde todo el repositorio y, debido a que la Berkeley DB es una librería de enlace dinámico, se puede necesitar instalar versiones de Berkeley DB actualizadas cuando se instalan nuevas versiones de Subversion. Subversion también proporciona dos protocolos de red alternativos para establecer la comunicación entre el cliente y el servidor. El primero es svnserve, un protocolo específico de Subversion que requiere tener un puerto abierto, o bien, habilitar el acceso por inetd o xinetd. El segundo protocolo es WebDAV sobre HTTP. Si tiene instalado en el servidor Apache 2.x el protocolo WebDAV le permite más control a la hora de especificar accesos, y se evita tener que abrir un puerto distinto al puerto 80 de web. Una característica única de Subversion es que permite almacenar metadatos asociados a los ficheros y directorios en forma de propiedades, es decir, pares key=value que pueden almacenar tanto metadatos propios del proyecto como metadatos de Subversion. Un ejemplo de propiedad es si el fichero debe ser interpretado en modo binario o en modo texto. Subversion también permite instalar en el repositorio hook scripts que se ejecutan al producirse una determinada acción (p.e. antes de hacer un commit o antes de cambiar una propiedad). Como indicamos en el apartado 7 del Tema 2, los hook script son una herramienta para automatizar tareas o comprobar el cumplimiento de reglas por parte de los usuarios. En el apartado 4 del Tema 9 se explicará su uso en detalle. Por último conviene destacar que Subversion introduce un API muy completo para que otros desarrolladores puedan crear herramientas gráficas de acceso a Subversion. En el apartado 6 del Tema 2 introdujimos estas herramientas. Subversion incorpora bindings para poder acceder a este API desde C, C++, Java, Perl y Python. 2. Instalación Antes de instalar Subversion conviene comprobar si tenemos instalado el siguiente software: APR. Apache Portable Runtime son un conjunto de librerías C que facilitan la creación de software portable entre plataformas. Esta librería fue elegida por los desarrolladores de Subversion para facilitar su portabilidad. Pág 95 Gestión de versiones con CVS y Subversion macprogramadores.org BerkeleyDB. Esta base de datos es un conjunto de librerías de enlace dinámico que permiten crear bases de datos embebidas a otros programas. Esta librería sólo es necesaria si vamos a elegir Berkeley DB para guardar el repositorio. Apache 2.x. Este conocido servidor web tiene que estar instalado, pero en su versión 2.x, que actualmente es menos usada, y no es compatible con la versión 1.x. Afortunadamente sólo se necesita instalar este servidor si se va a utilizar WebDAV para acceso al repositorio. A partir de Mac OS X 10.5 Subversion se distribuye preinstalado. En Mac OS X 10.4 o anterior, quizá, la forma más sencilla de instalar SVN es descargárselo del proyecto Fink, ya que este proyecto se encarga de comprobar las dependencias con las librerías anteriores, y las descarga si es necesario. Subversion consta de cuatro comandos principales: svn Es un comando usado por los clientes para trabajar con Subversion. snvserve es un demonio que actúa como servidor para permitir el acceso a Subversion a través de una red usando un protocolo de red propio de Subversion. svnadmin Es un comando usado por el administrador de Subversion. svnversion Permite conocer la versión de Subversion que tenemos instalada. 3. Configuración Una vez instalado Subversion, lo siguiente que tenemos que hacer es configurarlo. Como hemos mencionado en el apartado anterior, existen dos formas básicas de conectarse con un servidor Subversion: (1) usando el protocolo svnserve y (2) usando WebDAV. En este documento sólo vamos a estudiar la primera, ya que la segunda, aunque es más flexible a la hora de configurar los permisos de acceso, resulta mucho más complicada y puede dar problemas a usuarios que tengan ya instalado Apache 1.x, como es el caso de los usuarios de Mac OS X. Si está interesado en configurar su servidor Subversion con WebDAV, puede buscar información en Internet. 3.1. Ejecutar como un demonio La forma más sencilla de ejecutar Subversion es como un demonio. Para ello simplemente ejecutamos el comando: $ svnserve --daemon --root=/var/svn La opción --daemon dice al comando svnserve que se ejecute como un demonio, es decir, que pase a background y que se desenganche de la shell. La opción --root permite indicar el directorio que actúa como raíz. Esta opción conviene siempre utilizarla ya que si no se usa, se considera que el repositorio nace en la raíz del servidor y los usuarios tendrían acceso a todo el disco del servidor. En la práctica, es habitual destinar el directorio /var/svn a actuar como directorio raíz de los proyectos Subversion. Es importante que el usuario que ejecuta svnserve tenga permiso de escritura sobre el directorio del repositorio. Por ejemplo, si en este directorio sólo puede escribir root debería ejecutarlo desde la cuenta de root. tenga en cuenta que al Pág 96 Gestión de versiones con CVS y Subversion macprogramadores.org ejecutar el comando con la opción --root=/var/svn los usuarios que interactúen con Subversion sólo podrán acceder a los ficheros del directorio /var/svn a través del demonio svnserve. Por defecto svnserve se instala en el puerto 3690, podemos cambiar el puerto con la opción --listen-port=puerto. Lógicamente, en lugar de ejecutar svnserve desde el terminal cada vez que arrancamos la máquina, resulta más cómodo escribir este comando en alguno de los scripts de arranque de la máquina. 3.2. Ejecutar con inetd o xinetd Una forma alternativa de ejecutar svnserve es ejecutarlo con inetd o xinetd. Estos superservidores lanzan svnserve cuando reciben una conexión por el puerto de Subversion y svnserve realiza toda la entrada y salida por su entrada y salida estándar, que es la forma en que los superservidores esperan interactuar con los servicios. Para que svnserve cambie su comportamiento para leer y escribir por la entrada y salida estándar debemos de pasarle la opción --inetd (en vez de la opción --daemon). Si vamos a utilizar svnserve desde un superservidor lo primero que tenemos que hacer es comprobar que en el fichero /etc/services haya una entrada que indique que a Subversion le corresponde el puerto 3690. Para ello compruebe que exista (o créela si no existe) una entrada de la forma: svn 3690/tcp #Subversion Si queremos ejecutar svnserve desde inetd debemos de introducir la siguiente entrada en el fichero /etc/inetd.conf: svn stream tcp nowait --inetd --root=/var/svn root /usr/bin/svnserve svnserve La cual indica que queremos ejecutar como root el comando svnserve al recibir tráfico por el puerto 3690. Si el directorio /var/svn tiene permisos para que accedan otros usuarios (p.e. los miembros del grupo users) quizá le convenga ejecutar este comando con menos privilegios. Si en vez de inetd nuestro sistema usa xinetd (p.e. Mac OS X) entonces debemos de crear en el subdirectorio /etc/xinetd un fichero de la forma: $ cat svn service svn { socket_type = stream protocol = tcp user = root wait = no server = /sw/bin/svnserve server_args = --inetd --root=/var/svn } El parámetro user en este caso es root, pero podemos usar otro usuario con menos privilegios que tenga permiso de escritura en /var/svn. El parámetro server indica la ruta del comando svnserve. Pág 97 Gestión de versiones con CVS y Subversion macprogramadores.org Tunneling sobre SSH Existe una tercera forma de conectar con el repositorio que consiste en que cada usuario del servidor tenga una cuenta en el servidor a la que pueda acceder por SSH. En este caso tendremos que instalar sshd en el servidor y ssh en los clientes. Mediante esta técnica el usuario entra en su cuenta en el servidor y trabaja localmente sobre el directorio del repositorio, con lo que no necesitamos tener ejecutando el comando svnserve en el servidor ni instalarlo en un superservidor. 4. Crear el repositorio Una vez configurado Subversion la siguiente tarea que tiene que hacer el administrador es crear el repositorio. Para crear el repositorio debemos de estar logados en la máquina que va a actuar como servidor y ejecutar el comando svnadmin, el cual se usa para la mayoría de las tareas de administración. En concreto para crear el repositorio ejecutamos: $ svnadmin create --fs-type fsfs /var/svn La opción --fs-type fsfs indica que queremos que se use FSFS para almacenar el repositorio. Este comando habrá creado una serie de ficheros y directorios en el directorio del repositorio que en nuestro caso es /var/svn: $ ls -l /var/svn -rw-r--r-- 1 flh drwxr-xr-x 2 flh drwxr-xr-x 2 flh drwxr-sr-x 5 flh -r--r--r-- 1 flh drwxr-xr-x 2 flh drwxr-xr-x 2 flh users users users users users users users 229 128 48 256 2 360 104 2007-08-11 2007-08-11 2007-08-11 2007-08-11 2007-08-11 2007-08-11 2007-08-11 21:48 21:48 21:48 21:48 21:48 21:48 21:48 README.txt conf/ dav/ db/ format hooks/ locks/ No debemos de intentar modificar los ficheros del directorio del repositorio directamente, sino que siempre lo haremos a través del comando de cliente svn como vamos a ver en el siguiente apartado. 5. Importar un proyecto En el apartado 1 indicamos que Subversion no implementa un mecanismo de tags y branches, sino que propone crear copias ligeras en el proyecto. Por esta razón, aunque no es obligatorio seguir este esquema, la mayoría de los proyectos gestionados con Subversion tienen tres subdirectorios: trunk donde se almacena la línea principal de desarrollo del proyecto, branches donde se hacen copias ligeras a partir de las cuales se crean ramas, y tags donde se hacen copias ligeras que sirven para guardar el estado del proyecto cuando se alcanza un determinado hito. En este apartado vamos a ver cómo importar un proyecto al repositorio. En nuestro caso vamos a crear un proyecto de ejemplo llamado saludos que va a contener dos ficheros hola.c y Makefile. Su contenido es el mismo que usamos Pág 98 Gestión de versiones con CVS y Subversion macprogramadores.org con CVS en el apartado 4 del Tema 3, y que volvemos a reproducir en el Listado 7.1 y Listado 7.2 por comodidad. #include <stdio.h> int main() { printf("Hola mundo controlado en Subversion"); return 0; } Listado 7.1: Programa hola.c hola : hola.o gcc hola.o -o hola hola.o : hola.c gcc -c hola.c Listado 7.2: Fichero Makefile A continuación, vamos a crear dentro de un directorio temporal la estructura de subdirectorios recomendada para un proyecto Subversion: $ $ $ $ $ mkdir mkdir mkdir mkdir mkdir tmp tmp/saludos tmp/saludos/trunk tmp/saludos/branches tmp/saludos/tags También debemos crear los ficheros hola.c y Makefile en el subdirectorio trunk: $ ls -l tmp/saludos/trunk -rw-r----- 1 flh users 67 2007-08-12 09:19 Makefile -rw-r----- 1 flh users 86 2007-08-12 09:19 hola.c Ahora es el momento de empezar a usar el comando svn, el cual tiene el formato general: svn command [options] [arguments] Donde command es el comando a ejecutar, options son opciones para el comando precedidas por uno o dos guiones, y arguments son argumentos adiciones que recibe el command. En nuestro caso vamos a empezar usando el comando import el cual tiene el formato: svn import [options] [path] url Donde path es el directorio local donde está el proyecto a importar al repositorio (si no se indica es el directorio actual), y url es la ruta en el servidor donde depositar el proyecto. Aunque en el apartado 7 veremos cómo acceder a repositorios remotos, de momento vamos a usar una ruta de tipo file:///, que indica que el repositorio se encuentra en el disco local. Luego para importar el directorio saludos al repositorio hacemos: Pág 99 Gestión de versiones con CVS y Subversion macprogramadores.org $ svn import -–message "Importamos el proyecto inicial" tmp/saludos file:///var/svn/saludos Adding saludos/trunk Adding saludos/trunk/hola.c Adding saludos/trunk/Makefile Adding saludos/branches Adding saludos/tags Committed revision 1. La opción -–message se usa para dar un mensaje de log que se asocia a la primera revisión del proyecto. Obsérvese que al no estar situados dentro del directorio saludos hemos tenido que darlo en el argumento path. También podríamos habernos metido dentro y no hubiera hecho falta dar este parámetro, es decir, podríamos haber hecho: $ cd tmp/saludos $ svn import -–message "Importamos el proyecto inicial" file:///var/svn/saludos En ambos casos se crea el proyecto saludos dentro del directorio de repositorio /var/svn. Pero tenga en cuanta que no se crea ningún directorio saludos en /var/svn: $ ls -l /var/svn -rw-r--r-- 1 flh drwxr-xr-x 2 flh drwxr-xr-x 2 flh drwxr-sr-x 5 flh -r--r--r-- 1 flh drwxr-xr-x 2 flh drwxr-xr-x 2 flh 6. users users users users users users users 229 128 48 256 2 360 104 2007-08-11 2007-08-11 2007-08-11 2007-08-12 2007-08-11 2007-08-11 2007-08-11 21:48 21:48 21:48 09:47 21:48 21:48 21:48 README.txt conf/ dav/ db/ format hooks/ locks/ Crear la working copy Una vez creado el proyecto saludos en el repositorio lo siguiente que tenemos que hacer es bajarnos una working copy, la cual contendrá los ficheros del proyectos junto con metadatos que le sirven a Subversion para saber donde se encuentra el repositorio y para sincronizar su contenido con el repositorio. Para ello ejecutamos el comando checkout que tiene el formato: svn checkout [options] url [path] En este caso url es la URL del proyecto en el repositorio y path es el directorio donde queremos crear la working copy. Si no se indica path se crea en el directorio actual. Luego para importar el proyecto saludos en el directorio home hacemos: $ cd $ svn checkout file:///var/svn/saludos saludos A saludos/trunk A saludos/trunk/hola.c A saludos/trunk/Makefile A saludos/branches A saludos/tags Checked out revision 1. Pág 100 Gestión de versiones con CVS y Subversion $ ls -l drwxr-xr-x drwxr-xr-x macprogramadores.org 6 flh users 144 2007-08-12 14:36 saludos/ 5 flh users 120 2007-08-12 09:17 tmp/ Tenga en cuenta que no es lo mismo el proyecto saludos que creamos en el directorio tmp para importarlo, que la working copy de saludos que tenemos en el directorio home. La segunda tiene ficheros de metadatos que le sirven a Subversion para sincronizarse con el repositorio. En concreto, en cada subdirectorio de la working copy habrá un subdirectorio .svn con estos metadatos: $ ls -la saludos drwxr-xr-x 6 flh users 144 2007-08-12 14:36 ./ drwxr-xr-x 45 flh users 2264 2007-08-12 14:36 ../ drwxr-xr-x 7 flh users 296 2007-08-12 14:36 .svn/ drwxr-xr-x 3 flh users 72 2007-08-12 14:36 branches/ drwxr-xr-x 3 flh users 72 2007-08-12 14:36 tags/ drwxr-xr-x 3 flh users 120 2007-08-12 14:36 trunk/ $ ls -la saludos/trunk total 8 drwxr-xr-x 3 flh users 120 2007-08-12 14:36 ./ drwxr-xr-x 6 flh users 144 2007-08-12 14:36 ../ drwxr-xr-x 7 flh users 296 2007-08-12 14:36 .svn/ -rw-r--r-- 1 flh users 67 2007-08-12 14:36 Makefile -rw-r--r-- 1 flh users 86 2007-08-12 14:36 hola.c A partir de este momento puede borrar la copia inicial del proyecto y trabajar siempre dentro de la working copy: $ rm –rf tmp $ cd saludos/trunk 7. Acceso a repositorios remotos En el apartado 3 vimos tres formas de configurar el servidor. En este apartado vamos a ver que la URL que usamos para acceder al repositorio depende de cómo hayamos configurado el servidor. Ya hemos visto que en caso de que el repositorio esté en el disco local la forma de acceder al repositorio es usando una URL de la forma file:///. En caso de que el servidor sea un demonio (o bien se lance con un superservidor), la forma de acceder es usando una URL de la forma svn://. Por ejemplo, para descargarnos una working copy del proyecto saludos desde una máquina distinta haríamos: $ svn checkout svn://localhost/saludos A saludos/trunk A saludos/trunk/hola.c A saludos/trunk/Makefile A saludos/branches A saludos/tags Checked out revision 1. Donde localhost es la máquina donde está el servidor. Tenga en cuenta que si al lanzar svnserve usó la opción --root=/var/svn, sólo tendrá que dar la ruta respecto a este directorio, es decir saludos (y no var/svn/saludos). Pág 101 Gestión de versiones con CVS y Subversion macprogramadores.org En este tutorial no vamos a ver cómo se configura un servidor con WebDAV, pero si tiene que acceder a un servidor con este protocolo, use una URL de la forma http:// o https:// tal como le indique el administrador del servidor. Por ejemplo, para acceder al proyecto saludos haríamos: $ svn checkout http://localhost/saludos A saludos/trunk A saludos/trunk/hola.c A saludos/trunk/Makefile A saludos/branches A saludos/tags Checked out revision 1. Por último, si hemos decidido usar tunneling con SSH para acceder al servidor debemos usar una URL de la forma svn+ssh://. Por ejemplo, si tiene una cuenta ssh en el servidor donde está el repositorio de Subversion, para acceder al proyecto saludos haríamos: $ svn checkout svn+ssh://flh@localhost/var/svn/saludos flh@localhost's password: ******* A saludos/trunk A saludos/trunk/hola.c A saludos/trunk/Makefile A saludos/branches A saludos/tags Checked out revision 1. En este caso tenemos que indicar el nombre de la cuenta SSH (flh en nuestro ejemplo). También en este caso no se ejecuta svnserve en el servidor, sino que svn se loga en el servidor usando SSH con lo que tendremos que dar la ruta completa del proyecto (/var/svn/saludos en nuestro ejemplo). Si no tiene activada la identificación por clave pública de SSH, posiblemente le pida un password. 8. Commit y update En este momento ya tenemos creada una working copy, con lo que podemos empezar a trabajar dentro de ella. En el apartado 1 del Tema 2 introdujimos la operación update, la cual nos permite bajarnos los cambios del proyecto del repositorio que otros programadores hayan hecho. Para ejecutarla simplemente ejecutamos el comando update de la forma: $ cd saludos/trunk $ svn update At revision 1. En este caso nadie ha modificado nada con lo que no se descarga ningún cambio. Más interesante resulta observar que al comando update no le hemos pasado la URL del proyecto del repositorio, esto se debe a que esta información está guardada en los ficheros de metadatos (subdirectorio .svn), con lo que siempre que trabajemos dentro de la working copy no hará falta indicar dónde se encuentra el proyecto del repositorio. Pág 102 Gestión de versiones con CVS y Subversion macprogramadores.org Si, por ejemplo, otro programador hubiera modificado el fichero Makefile, al ejecutar update hubiéramos recibido un mensaje de la forma: $ svn update U Makefile Updated to revision 2. Donde la U indica que se ha actualizado el fichero y vemos que nos bajamos la revisión 2 del proyecto. Por contra, podría ser que fuéramos nosotros los que modificásemos un fichero de la working copy. Por ejemplo, imaginemos que modificamos el fichero hola.c, para subir el fichero al repositorio debemos hacer un commit de la forma: $ svn commit Sending trunk/hola.c Transmitting file data . Committed revision 3. Al hacer el commit se nos pide un mensaje de log, para lo cual se lanza el editor de texto por defecto. Si queremos que no se lance el editor podríamos haber usado la opción –-message para dar el mensaje como una opción del comando commit. 9. Estado de los ficheros de la working copy Podemos usar el comando status para conocer el estado de los ficheros de la working copy. Por ejemplo: $ svn status ? README.txt M hola.c A cada fichero modificado se le precede por un símbolo de estado de acuerdo a la Tabla 8.1. En el ejemplo anterior, ? significa que el fichero existe en el directorio de la working copy pero no forma parte del proyecto (no existe información de él en el subdirectorio .svn ), y M significa que el fichero ha sido modificado en la working copy, pero no se han subido los cambios al repositorio. Pág 103 Gestión de versiones con CVS y Subversion macprogramadores.org Tema 8 Subversion desde el punto de vista del usuario Sinopsis: En el tema anterior dimos una introducción rápida al uso de Subversion. En este tema pretendemos estudiar con más detenimiento las muchas opciones que proporciona Subversion al usuario. Empezaremos viendo cómo interactuar con el repositorio, conocer los cambios que otros están haciendo, y resolver posibles conflictos. Para acabar el tema estudiaremos cómo se hace el tagging y branching en Subversion, así como el uso de las propiedades, una característica única de Subversion para almacenar metadatos en el repositorio. Pág 104 Gestión de versiones con CVS y Subversion 1. macprogramadores.org El cliente de Subversion Cuando los clientes quieren interactuar con el repositorio, la herramienta que usan es el comando svn. En el apartado 5 del Tema 7 ya adelantamos que el formato general de este comando es: svn command [options] [arguments] Cada command (p.e. import, checkout, commit o update) tiene sus propias opciones y argumentos. Podemos pedir ayuda sobre su formato usando la opción -help. Por ejemplo: $ svn checkout --help checkout (co): Check out a working copy from a repository. usage: checkout URL[@REV]... [PATH] If specified, REV determines in which revision the URL is first looked up. If PATH is omitted, the basename of the URL will be used as the destination. If multiple URLs are given each will be checked out into a sub-directory of PATH, with the name of the sub-directory being the basename of the URL. Valid options: -r [--revision] arg : : -q [--quiet] : -N [--non-recursive] : --username arg : --password arg : --no-auth-cache : --non-interactive : --config-dir arg : : --ignore-externals : ARG (some commands also take ARG1:ARG2 range) print as little as possible operate on single directory only specify a username ARG specify a password ARG do not cache authentication tokens do no interactive prompting read user configuration files from directory ARG ignore externals definitions Conviene tener clara la diferencia entre un path, que es una ruta en el disco del cliente, y una URL que es una ruta en el repositorio. También podemos preguntar por los comandos de que dispone svn de la forma: $ svn --help usage: svn <subcommand> [options] [args] Subversion command-line client, version 1.3.2. Type 'svn help <subcommand>' for help on a specific subcommand. Most subcommands take file and/or directory arguments, recursing on the directories. If no arguments are supplied to such a command, it recurses on the current directory (inclusive) by default. Available subcommands: add blame (praise, annotate, ann) cat checkout (co) cleanup commit (ci) copy (cp) Pág 105 Gestión de versiones con CVS y Subversion macprogramadores.org delete (del, remove, rm) diff (di) export help (?, h) import info list (ls) lock log merge mkdir move (mv, rename, ren) propdel (pdel, pd) propedit (pedit, pe) propget (pget, pg) proplist (plist, pl) propset (pset, ps) resolved revert status (stat, st) switch (sw) unlock update (up) Vemos que muchos de estos comandos tienen abreviaturas, Por ejemplo el comando update también se puede escribir como up. 2. Exportar un proyecto En el apartado 6 del Tema 7 aprendimos que el comando checkout nos permite crear una working copy a partir del repositorio. Este comando está bien para los programadores que van a modificar un proyecto, pero tiene la característica de que crea directorios .svn con metadatos. Si un usuario sólo desea obtener una versión de un software, pero no desea participar en su desarrollo, quizá prefiera usar el comando export, que también le devuelve el contenido del repositorio, pero sin metadatos. El comando export también nos puede ser útil cuando queramos distribuir una versión de nuestro software. Por ejemplo, para obtener la última versión de nuestro proyecto saludos podemos hacer: $ svn export file:///var/svn/saludos A saludos A saludos/trunk A saludos/trunk/hola.c A saludos/trunk/Makefile A saludos/branches A saludos/tags Exported revision 3. 3. Layout del repositorio Imaginemos que ahora otro grupo de trabajo desea crear otro proyecto llamado despidos destinado a producir mensajes para despedirse. Como el repositorio ya está creado (comando create ), lo único que tienen que hacer es importar (comando Pág 106 Gestión de versiones con CVS y Subversion macprogramadores.org import) los ficheros de su proyecto al repositorio. Por ejemplo, creamos la estructura del proyecto: $ $ $ $ $ mkdir mkdir mkdir mkdir mkdir tmp tmp/despidos tmp/despidos/trunk tmp/despidos/branches tmp/despidos/tags Y creamos en el trunk los ficheros del proyecto: $ ls -l tmp/despidos/trunk -rw-r--r-- 1 flh users 75 2007-08-15 08:04 Makefile -rw-r--r-- 1 flh users 75 2007-08-15 08:04 adios.c Con lo que ahora podemos importar el proyecto: $ svn import –message "Mensajes para despedirse educadamente" tmp/despidos file:///var/svn/despidos Adding tmp/despidos/trunk Adding tmp/despidos/trunk/adios.c Adding tmp/despidos/trunk/Makefile Adding tmp/despidos/branches Adding tmp/despidos/tags Committed revision 4. Observe que el número de revisión es 4. Esto se debe a que realmente el proyecto saludos y el proyecto despidos comparten el mismo repositorio (el que creamos con el comando create ). En Subversion existen dos formas de estructurar un repositorio: (1) monolitic layout, donde cada proyecto tiene su propio repositorio, y (2) multiproject layout, donde varios proyectos comparten el mismo repositorio. Cada forma de estructurar el repositorio tiene sus ventajas e inconvenientes. En el monolitic layout tenemos que ejecutar los comandos create e import por cada proyecto. La ventaja es que los cambios en un proyecto no afectan al otro proyecto. En multiproject layout ejecutamos sólo un comando create y luego tantos comandos import como proyectos queramos subir al repositorio. El multiproject layout tiene la ventaja de que es más fácil compartir información entre los proyectos. Por ejemplo, si un proyecto es una librería de renderizado gráfico, y otro proyecto es una aplicación que usa esta librería. Para acabar el apartado vamos a cambiar la estructura de nuestro repositorio para que pase a ser monolitic layout, es decir, un repositorio para cada proyecto. Para ello empezamos exportando los proyectos a un directorio temporal: $ mkdir tmp $ cd tmp $ svn export file:///var/svn/saludos saludos A saludos A saludos/trunk A saludos/trunk/hola.c A saludos/trunk/Makefile A saludos/branches A saludos/tags Exported revision 4. $ svn export file:///var/svn/despidos despidos A despidos Pág 107 Gestión de versiones con CVS y Subversion macprogramadores.org A despidos/trunk A despidos/trunk/adios.c A despidos/trunk/Makefile A despidos/branches A despidos/tags Exported revision 4. $ ls -l drwxr-xr-x 5 flh users 120 2007-08-15 08:29 despidos/ drwxr-xr-x 5 flh users 120 2007-08-15 08:28 saludos/ Borramos el repositorio multiproject layout que teníamos: $ rm -fr /var/svn/* Y creamos un directorio y repositorio por cada proyecto: $ $ $ $ mkdir /var/svn/saludos svnadmin create --fs-type fsfs /var/svn/saludos mkdir /var/svn/despidos svnadmin create --fs-type fsfs /var/svn/despidos Observe que ahora se ha creado un repositorio en cada subdirectorio: $ ls /var/svn despidos/ saludos/ $ ls /var/svn/saludos README.txt conf/ dav/ $ ls /var/svn/despidos README.txt conf/ dav/ db/ format hooks/ locks/ db/ format hooks/ locks/ Finalmente importamos los proyectos: $ svn import --message "Proyecto destinado a dar saludar" saludos file:///var/svn/saludos Adding saludos/trunk Adding saludos/trunk/hola.c Adding saludos/trunk/Makefile Adding saludos/branches Adding saludos/tags Committed revision 1. $ svn import --message "Proyecto destinado a dar despedir" despidos file:///var/svn/despidos Adding despidos/trunk Adding despidos/trunk/adios.c Adding despidos/trunk/Makefile Adding despidos/branches Adding despidos/tags Committed revision 1. 4. Mantener actualizada la working copy Ya sabemos que la working copy se obtiene del repositorio con el comando checkout (o abreviado co ), el cual recibe la URL del proyecto en el repositorio y el path del directorio donde crear la working copy. Si no indicamos path de directorio destino se crea la working copy en el directorio actual. Luego para crear la working Pág 108 Gestión de versiones con CVS y Subversion macprogramadores.org copy del proyecto saludos en el subdirectorio saludos de nuestra máquina hacemos: $ svn checkout file:///var/svn/saludos saludos A saludos/trunk A saludos/trunk/hola.c A saludos/trunk/Makefile A saludos/branches A saludos/tags Checked out revision 1. Si por alguna razón no queremos que se bajen a la working copy los subdirectorios del proyecto podemos pasar a checkout la opción –-non-recursive (o abreviada N). En este caso los update que ejecutemos sobre la working copy tampoco bajarán los subdirectorios, pero podemos forzar a que se baje un subdirectorio pasando a update su nombre como argumento. También podemos pedir a checkout que baje sólo un subdirectorio del repositorio. Para ello damos la URL del subdirectorio. Por ejemplo, si queremos trabajar sólo con el trunk del proyecto del repositorio podemos hacer: $ rm -rf saludos $ svn checkout file:///var/svn/saludos/trunk saludos A saludos/hola.c A saludos/Makefile Checked out revision 1. Obsérvese que hemos necesitado borrar primero la working copy para luego bajarnos sólo el subdirectorio trunk. También sabemos que una vez que tenemos la working copy podemos bajarnos cambios que otros programadores suban al repositorio con el comando update, el cual por defecto se ejecuta de forma recursiva, pero podemos pedir que se ejecute de forma no recursiva con la opción -–non-recursive. Por ejemplo, si queremos actualizar nuestra working copy podemos hacer: $ svn update U hola.c A leeme.txt En este caso el fichero hola.c había sido modificado por otro programador y update lo ha actualizado U en nuestra working copy. También el fichero leeme.txt había sido añadido A al repositorio y update nos lo ha bajado a nuestra working copy. Los símbolos de estado de Subversion se resumen en la Tabla 8.1. Símbolo U M G C A Descripción El fichero había sido modificado en el repositorio y ha sido modificado en la working copy. El fichero está modificado en la working copy. Se ha mezclado el fichero del repositorio con el fichero de la working copy (que también estaba modificado). Hay un conflicto al intentar mezclar el fichero del repositorio con el de la working copy. El fichero ha sido añadido a la working copy y será añadido al repositorio en el próximo commit, o bien un fichero añadido al repositorio (por otro Pág 109 Gestión de versiones con CVS y Subversion D X ? ! macprogramadores.org usuario) ha sido añadido a nuestra working copy. El fichero ha sido eliminado de la working copy y será eliminado del repositorio en el próximo commit, o bien el fichero borrado del repositorio (por otro usuario) ha sido borrado de nuestra working copy. Referencia externa. Ver apartado 11.4.1 del Tema 8. El fichero no está bajo control de versiones. El fichero está bajo control de versiones pero ha sido borrado de la working copy usando una herramienta distinta a Subversion. Tabla 8.1: Símbolos de estado de los ficheros de proyecto Si modificamos un fichero en local, y luego nos arrepentimos de los cambios efectuados, siempre podemos volver al estado del fichero en el repositorio con el comando revert. Por ejemplo, si hemos modificado Makefile y queremos descartar los cambios hacemos: $ svn revert Makefile Reverted 'Makefile' Debido a que revert descarta cambios en la working copy (los cuales no están guardados en el repositorio), como medida de precaución revert por defecto es no recursivo. La no recursividad sólo actúa cuando pasamos a revert como nombre de fichero *. Una forma alternativa de volver al estado de un fichero en el repositorio es borrándolo, y pidiendo a Subversion que lo actualice: $ rm Makefile $ svn update Restored 'Makefile' At revision 2. 5. Modificar el proyecto En el apartado 8 del Tema 7 indicamos que cuando nosotros hemos modificado un fichero del proyecto, podemos usar el comando commit para subir los cambios al proyecto. Sin embargo, no debemos ejecutar commit sin haber comprobado que no hay cambios en el repositorio ya que Subversion (al igual que la mayoría de los gestores de versiones) sólo sabe mezclar ficheros en la working copy (no en el repositorio). Si al hacer commit Subversion detectase que el fichero que estamos intentando subir ha sido cambiado en el repositorio (por otro programador), se produciría un conflicto de la forma: $ svn commit Sending trunk/hola.c Transmitting file data .svn: Commit failed (details follow): svn: Base checksum mismatch on '/trunk/hola.c': expected: fc471fda66bee440c6a30a9b463d73d1 actual: 65cb94832a8e6d04c4a8556050833748 Es decir, Subversion ha detectado que el fichero que hay en el repositorio es distinto al último fichero que se bajó (con el comando update) y no nos deja hacer el commit. Para evitar este problema se recomienda hacer siempre commit en tres pasos: Pág 110 Gestión de versiones con CVS y Subversion macprogramadores.org 1. Ejecutar update para actualizar los posibles cambios del repositorio. 2. Ejecutar status para hacernos una idea de los ficheros que se van a subir al repositorio. 3. Ejecutar commit para subir los cambios al repositorio. En nuestro ejemplo haríamos: $ svn update G hola.c Updated to revision 3. $ svn status M hola.c $ svn commit Sending trunk/hola.c Transmitting file data . Committed revision 4. Al ejecutar el comando update obtenemos el símbolo de estado G para el fichero hola.c, que significa que el fichero estaba modificado tanto localmente como en el repositorio y que se ha mezclado los cambios del repositorio en el fichero local. Al ejecutar el comando status seguimos obteniendo el símbolo de estado M porque el fichero continua modificado localmente. Al ejecutar commit subimos los cambios locales al repositorio. Al ejecutar update, status o commit sin parámetros nos estamos refiriendo a los ficheros del directorio actual de forma recursiva. El comando update no tendrá efecto cuando nadie haya modificado el repositorio, pero siempre conviene ejecutarlo. Otra recomendación que no debemos olvidar antes de usar el comando commit es compilar y ejecutar previamente nuestro proyecto para asegurarnos de que sólo subimos al repositorio ficheros estables. 5.1. El editor por defecto Cada vez que ejecutamos commit sin la opción --message se lanza el editor por defecto para pedirnos un mensaje de log. Subversion busca el editor por defecto en las variables de entorno $SVN_EDITOR, $VISUAL y $EDITOR, en este orden. Por ejemplo, para usar por defecto el editor de textos joe podemos hacer: $ export EDITOR=joe Este será el editor que se use siempre que no estén declaradas las variables de entorno $SVN_EDITOR ni $VISUAL. 5.2. Añadir ficheros al proyecto Añadir ficheros al proyecto es un proceso en dos fases: Primero tenemos que añadir el fichero a la working copy con el comando add, después subimos el fichero al repositorio con el comando commit. Por ejemplo: $ svn status ? README.txt Pág 111 Gestión de versiones con CVS y Subversion macprogramadores.org En este caso hemos creado un nuevo fichero README.txt en el proyecto, pero no lo hemos añadido al proyecto. Para añadirlo hacemos: $ svn add README.txt A README.txt El comando add por defecto es recursivo, si no queremos que lo sea podemos usar la opción –-non-recursive. Ahora el fichero está añadido al proyecto en la working copy, pero para guardarlo en el repositorio hacemos un commit: $ svn update $ svn status A README.txt $ svn commit Adding trunk/README.txt Transmitting file data . Committed revision 3. Como dijimos, conviene siempre hacer un update y un status antes del commit para asegurarnos de que lo que vamos a subir al repositorio esté sincronizado, y para saber lo que vamos a subir al repositorio. En caso de que hayamos añadido un fichero a la working copy (comando add ) pero todavía no lo hayamos subido al repositorio (comando commit ) podemos deshacer el efecto del comando add con el comando revert: $ svn revert README.txt Reverted 'README.txt' $ svn status ? README.txt Para volver a añadir el fichero al repositorio podemos volver a ejecutar el comando add: $ svn add README.txt A README.txt Si el fichero ya ha sido añadido al repositorio, la forma de borrarlo del repositorio es la que vamos a ver en el siguiente apartado. 5.3. Borrar ficheros del proyecto El comando delete (también llamado remove, del o rm) permite borrar un fichero de la working copy y lo marca para ser borrado del repositorio en el próximo commit. Por ejemplo, para borrar el fichero que hemos añadido en el apartado anterior podemos hacer: $ svn remove README.txt D README.txt Este comando borra el fichero README.txt de la working copy, pero no del repositorio, con lo que todavía podemos deshacer el efecto de delete con el comando revert. $ svn revert README.txt Pág 112 Gestión de versiones con CVS y Subversion macprogramadores.org Reverted 'README.txt' El cual recuperaría el fichero borrado y le quitaría la marca de D. Cuando vamos a borrar un fichero debemos de tener cuidado de que no esté modificado localmente, porque si no Subversion nos impedirá la operación de borrado y nos advertirá de que el fichero que pretendemos borrar tiene cambios que no se han guardado en el repositorio. $ svn delete README.txt svn: Use --force to override this restriction svn: 'README.txt' has local modifications Siempre podemos usar la opción de comando –-force para forzar el borrado del fichero. En cualquier caso, después delete deberemos ejecutar commit para que los cambios se suban al repositorio. Por último comentar que también podemos pasar a delete la URL en el repositorio del fichero a borrar, en este caso no es necesario usar luego commit, ya que el borrado del fichero se produciría directamente en el repositorio. $ svn delete file:///var/svn/saludos/trunk/README.txt D file:///var/svn/saludos/trunk/README.txt Tenga en cuenta que en este caso el fichero README.txt no se borrará de la working copy hasta que ejecutemos update. $ svn update D README.txt Updated to revision 4. 5.4. Crear y borrar subdirectorios Los subdirectorios se crean y se borran igual que los ficheros, usando los comandos add y delete respectivamente. Por ejemplo, para crear el directorio doc hacemos: $ mkdir doc $ svn status ? doc $ svn add doc A doc $ svn commit A trunk/doc Committed revision 5. Y para borrarlo haríamos: $ svn delete doc D doc $ svn commit Deleting trunk/doc Committed revision 6. Pág 113 Gestión de versiones con CVS y Subversion 5.5. macprogramadores.org Modificar la estructura del proyecto Uno de los puntos fuertes de Subversion es la facilidad con la que podemos mover y copiar ficheros dentro del proyecto. Todos los movimientos y copias de fichero son ligeros, es decir, no se copia físicamente el fichero, sino que simplemente se apunta al fichero del que proviene el fichero destino. Para mover y copiar ficheros se usan los comandos move y copy respectivamente. Ambos comandos sólo modifican la estructura de la working copy, pero no se modifica el repositorio hasta que ejecutemos commit. En caso de que ejecutemos move o copy con URLs de repositorio, al igual que antes, lo que ocurre es que se modifican los ficheros en el repositorio pero no en la working copy (hasta que ejecutemos update). Por ejemplo, si queremos mover el fichero README.txt a el subdirectorio doc podemos hacer: $ svn move README.txt doc A doc/README.txt D README.txt $ svn status A + doc/README.txt D README.txt $ svn commit --message "Movido fichero README.txt a doc" Deleting trunk/README.txt Adding trunk/doc/README.txt Committed revision 5. El símbolo + que aparece junto al fichero README.txt sirve para indicar que el fichero no es nuevo sino que arrastra una historia desde otro fichero. Obsérvese también que el comando move muestra el fichero borrado junto con el fichero añadido, esto es porque ejecutar move es equivalente a haber ejecutado copy seguido de delete. 6. Obtener información sobre el proyecto En este apartado vamos a estudiar diversos comandos que nos permiten obtener información sobre los ficheros del proyecto. 6.1. El comando status En el apartado 9 del Tema 7 ya introducimos el comando status. Este comando por defecto sólo nos muestra los cambios en los ficheros de la working copy. Para ello usa el subdirectorio .svn para detectar los ficheros modificados, pero no contacta con el repositorio. Es decir, muestra símbolos de estado M para los ficheros modificados en la working copy, pero no muestra símbolos de estado U para los ficheros que han sido modificados en el repositorio. Si queremos que se muestre información sobre todos los ficheros de la working copy podemos usar la opción –verbose (o bien -v) para obtener esta información: $ svn status M Makefile $ svn status -v 3 3 flh . Pág 114 Gestión de versiones con CVS y Subversion M 4 3 4 1 2 3 4 1 flh flh flh flh macprogramadores.org hola.c doc doc/README.txt Makefile En caso de usar la opción –-verbose, siguen si mostrarse los ficheros modificados en el repositorio, pero se muestran los siguientes campos por cada fichero de la working copy: (1) El símbolo de estado, (2) la última revisión en que se modificó el fichero en la working copy. Este número se representa con BASE y puede ser distinto para cada fichero. (3) Después aparece la última revisión en que se actualizó el fichero en el repositorio. (4,5) Y finalmente se muestra quién modificó el fichero por última vez en el repositorio, y el nombre del fichero. La razón por la que hola.c tiene un 4 en la última revisión de la working copy, y sólo un 2 en la última revisión del repositorio es porque en la revisión 4 lo borramos y nos lo tuvimos que volver a bajar (aunque la última vez que modificamos su contenido fue en la revisión 2). En ocasiones podemos desear conocer qué ficheros están modificados en el repositorio (y en consecuencia serán actualizados en la working copy en el próximo update), en este caso podemos usar la opción –-show-updates (o bien -u): $ svn status -u * 4 hola.c M 1 Makefile Status against revision: 5 La opción –-show-updates hace que status contacte con el repositorio para mostrar tres nuevas informaciones: Por un lado muestra los ficheros que han sido modificados en el repositorio marcándolos con un * (en nuestro ejemplo hola.c está modificado en el repositorio). Por otro lado muestra la última revisión en que se modificó el fichero en la working copy (es decir, la BASE), y por último muestra la última revisión del repositorio. A la última revisión del repositorio se la llama HEAD, y a diferencia de BASE es única para todos los ficheros, y en nuestro ejemplo es la revisión 5. Podemos combinar las opciones --verbose y --show-updates para obtener ambas informaciones: $ svn status -u -v 4 4 flh 3 3 flh * 4 2 flh M 1 1 flh 3 3 flh Status against revision: doc/README.txt doc hola.c Makefile . 5 En este ejemplo BASE vale respectivamente 4,3,4,1,3, HEAD vale 5 y las últimas revisiones en que se actualizaron los ficheros en el repositorio son respectivamente 4,3,2,1,3. Obsérvese que en este ejemplo hola.c tiene una revisión 5 en el repositorio, pero nuestro cliente de Subversion todavía no lo sabe, tan sólo conoce el * de que hola.c ha sido modificado. 6.2. Obtener información detallada de un fichero Podemos preguntar por toda la información sobre un fichero que Subversion guarda en el subdirectorio .svn usando el comando info : Pág 115 Gestión de versiones con CVS y Subversion macprogramadores.org $ svn info hola.c Path: hola.c Name: hola.c URL: file:///var/svn/saludos/trunk/hola.c Repository Root: file:///var/svn/saludos Repository UUID: d05e2aa6-b937-0410-8a7c-96705f58291f Revision: 4 Node Kind: file Schedule: normal Last Changed Author: flh Last Changed Rev: 2 Last Changed Date: 2007-08-15 11:35:56 +0200 (Wed, 15 Aug 2007) Text Last Updated: 2007-09-08 18:25:35 +0200 (Sat, 08 Sep 2007) Properties Last Updated: 2007-09-08 18:25:35 +0200 (Sat, 08 Sep 2007) Checksum: 3d8fec2c00d1839e0da3a8a53a003ff4 En este caso el campo Revision vuelve a indicar la última revisión en que se modificó el fichero en la working copy, mientras que el campo Last Changed Rev indica la última vez en que el fichero se actualizó en el repositorio. Debido a que todavía no hemos ejecutado update, Subversion todavía no sabe que hay una revisión 5 en el repositorio. Pero podemos actualizarnos y vemos que estos números cambian: $ svn update Restored 'hola.c' U hola.c Updated to revision 5. $ svn info hola.c Path: hola.c Name: hola.c URL: file:///var/svn/saludos/trunk/hola.c Repository Root: file:///var/svn/saludos Repository UUID: d05e2aa6-b937-0410-8a7c-96705f58291f Revision: 5 Node Kind: file Schedule: normal Last Changed Author: flh Last Changed Rev: 5 Last Changed Date: 2007-09-09 11:08:41 +0200 (Sun, 09 Sep 2007) Text Last Updated: 2007-09-09 11:44:37 +0200 (Sun, 09 Sep 2007) Properties Last Updated: 2007-09-09 11:44:37 +0200 (Sun, 09 Sep 2007) Checksum: 2cae656546ad44f12f61c7650b74705f Si ejecutamos info sin indicar un nombre de fichero, se muestra información sobre todos los ficheros del directorio actual (de forma no recursiva), lo cual produce una salida muy larga, con lo que normalmente usaremos info junto con el nombre de un fichero. 6.3. Obtener información de log Podemos obtener información de los logs que se proporcionaron al hacer commit de las distintas revisiones con el comando log. Si ejecutamos log sin indicar fichero se muestran los cambios en el directorio actual: $ svn log --------------------------------------------------------------------- Pág 116 Gestión de versiones con CVS y Subversion macprogramadores.org r4 | flh | 2007-09-09 10:55:09 +0200 (Sun, 09 Sep 2007) | 2 lines Movido fichero README.txt a doc --------------------------------------------------------------------r3 | flh | 2007-09-08 18:26:24 +0200 (Sat, 08 Sep 2007) | 2 lines Fichero de documentacion --------------------------------------------------------------------r2 | flh | 2007-08-15 11:35:56 +0200 (Wed, 15 Aug 2007) | 2 lines Cambio de mensaje --------------------------------------------------------------------r1 | flh | 2007-08-15 11:34:00 +0200 (Wed, 15 Aug 2007) | 1 line Empieza el proyecto de saludos --------------------------------------------------------------------- Sin embargo, si lo ejecutamos sobre un fichero se muestran sólo los log de los cambios que afectan a ese fichero. Por ejemplo, en la siguiente ejecución vemos que el fichero hola.c ha sido modificado sólo en las revisiones 1 y 2. $ svn log hola.c --------------------------------------------------------------------r2 | flh | 2007-08-15 11:35:56 +0200 (Wed, 15 Aug 2007) | 2 lines Cambio de mensaje --------------------------------------------------------------------r1 | flh | 2007-08-15 11:34:00 +0200 (Wed, 15 Aug 2007) | 1 line Empieza el proyecto de saludos --------------------------------------------------------------------- Al ejecutar log podemos indicar tanto un fichero de la working copy como un fichero del repositorio. Cuando ejecutamos log sobre un fichero de la working copy se muestran sólo los cambios hasta la revisión BASE (que recuérdese que es la última revisión con la que hemos sincronizado la working copy), mientras que si ejecutamos log sobre un fichero del repositorio se muestran los cambios hasta HEAD (que recuérdese que es la última revisión guardada en el repositorio). Por ejemplo: $ svn log file:///var/svn/saludos/trunk/hola.c --------------------------------------------------------------------r5 | flh | 2007-09-09 11:08:41 +0200 (Sun, 09 Sep 2007) | 2 lines Añadidos parámetros --------------------------------------------------------------------r2 | flh | 2007-08-15 11:35:56 +0200 (Wed, 15 Aug 2007) | 2 lines Cambio de mensaje --------------------------------------------------------------------r1 | flh | 2007-08-15 11:34:00 +0200 (Wed, 15 Aug 2007) | 1 line Empieza el proyecto de saludos --------------------------------------------------------------------- Pág 117 Gestión de versiones con CVS y Subversion macprogramadores.org En este caso, además de un log para la revisiones 1 y 2 aparece un log para la revisión 5. Esto se debe a que alguien ha subido un cambio en hola.c para la revisión 5, sin embargo como nosotros no nos hemos actualizado (nuestra BASE es 4) no disponemos de este cambio en la working copy. Es importante destacar que cuando ejecutamos log sin parámetros estamos preguntando por la información de log respecto a la BASE del directorio actual, que no siempre tiene porque ser igual a HEAD (la última revisión), sino que puede haber dentro del directorio actual ficheros con una revisión mayor cuya información de log no salga. Por ejemplo, si modificamos y subimos el fichero hola.c al repositorio y luego ejecutamos log sin nombre de fichero obtenemos: $ svn commit Sending trunk/hola.c Transmitting file data . Committed revision 6. $ svn status -v 4 4 flh . 6 6 flh hola.c 4 4 flh doc 4 4 flh doc/README.txt 4 1 flh Makefile $ svn log --------------------------------------------------------------------r4 | flh | 2007-09-09 10:55:09 +0200 (Sun, 09 Sep 2007) | 2 lines Movido fichero README.txt a doc --------------------------------------------------------------------r3 | flh | 2007-09-08 18:26:24 +0200 (Sat, 08 Sep 2007) | 2 lines Fichero de documentacion --------------------------------------------------------------------r2 | flh | 2007-08-15 11:35:56 +0200 (Wed, 15 Aug 2007) | 2 lines Cambio de mensaje --------------------------------------------------------------------r1 | flh | 2007-08-15 11:34:00 +0200 (Wed, 15 Aug 2007) | 1 line Empieza el proyecto de saludos --------------------------------------------------------------------- Es decir, aunque HEAD vale 6, sólo se muestra la información de log hasta la BASE del directorio actual, es decir, hasta la revisión 4. Si ejecutásemos update y se actualizaría la BASE del directorio actual, ya podríamos obtener información de log hasta la última revisión. $ svn update At revision 6. $ svn status -v 6 6 6 6 M 6 6 6 4 4 1 flh flh flh flh flh . hola.c doc doc/README.txt Makefile Pág 118 Gestión de versiones con CVS y Subversion macprogramadores.org Por último comentar que en Subversion cada revisión implica un cambio en uno o más ficheros, pero en principio no sabemos cuáles son los ficheros cambiados en cada revisión. Podemos usar la opción --verbose del comando log para obtener esta información: $ svn log --verbose hola.c --------------------------------------------------------------------r5 | flh | 2007-09-09 11:08:41 +0200 (Sun, 09 Sep 2007) | 2 lines Changed paths: M /trunk/hola.c Añadidos parámetros --------------------------------------------------------------------r2 | flh | 2007-08-15 11:35:56 +0200 (Wed, 15 Aug 2007) | 2 lines Changed paths: M /trunk/hola.c Cambio de mensaje --------------------------------------------------------------------r1 | flh | 2007-08-15 11:34:00 +0200 (Wed, 15 Aug 2007) | 1 line Changed paths: A /branches A /tags A /trunk A /trunk/Makefile A /trunk/hola.c Empieza el proyecto de saludos --------------------------------------------------------------------- En este ejemplo se ve que en la revisión 1 los ficheros afectados fueron varios (todos los que usamos para crear el proyecto inicial), mientras que en la revisión 2 y 5 el único fichero que se modificó fue hola.c. 6.4. Identificar culpables Otro comando interesante es el comando blame, que nos permite identificar quién ha hecho los últimos cambios en cada línea de un fichero y en qué revisión hizo los cambios: $ svn blame hola.c 1 flh #include <stdio.h> 1 flh 5 ana int main(int argc, char* argv[]) 1 flh { 2 ana printf("Hola mundo controlado por Subversion\n"); 1 flh return 0; 1 flh } En este ejemplo vemos que todas las líneas del fichero las creo flh en la revisión 1 y que luego ana modificó el fichero en la revisión 2 y 5. El comando blame se suele usar para identificar culpables cuando se encuentra un bug, pero hay que tener cuidado antes de acusar a alguien, ya que si un usuario sólo modifica el indentado Pág 119 Gestión de versiones con CVS y Subversion macprogramadores.org de una línea, este usuario aparecerá como responsable del último cambio en la línea, cuando en realizad el bug puede haber sido introducido por otro usuario anterior. 6.5. Obtener revisiones anteriores En ocasiones queremos poder conocer los ficheros que forman un determinado repositorio. Una opción es hacer un checkout del repositorio, pero esto consume tiempo y ancho de banda. Si sólo nos interesa conocer los ficheros que forman parte de un repositorio una buena opción es el comando list, el cual contacta con el repositorio y devuelve un listado de los ficheros del repositorio. Normalmente ejecutaremos el comando list pasándole la URL del directorio en el repositorio a listar: $ svn list file:///var/svn/saludos/trunk Makefile doc/ hola.c Pero si estamos dentro de la working copy también podemos pasar el nombre de un fichero de la working copy y list usa la URL del fichero en la working copy. También podemos no pasar nombre de fichero al comando list, para referirnos al directorio actual en el repositorio: $ svn list Makefile doc/ hola.c En ambos casos lo que se muestra es el contenido del repositorio, nunca el de la working copy. Y también en ambos caso se muestra la revisión HEAD, es decir, la última revisión en el repositorio. Más interesante resulta el poder consultar por los ficheros que forman parte del repositorio en revisiones anteriores, para lo cual usamos la opción --revision. Por ejemplo para obtener un listado del contenido del proyecto en la revisión 1 podemos hacer: $ svn list --revision 1 Makefile hola.c Vemos que en la revisión 1 todavía no existía el subdirectorio doc. También podemos pedir un listado detallado con la opción --verbose: $ svn list --revision 1 --verbose 1 flh 80 Aug 15 11:34 Makefile 1 flh 95 Aug 15 11:34 hola.c En este caso las fechas corresponden a la revisión 1. También podemos pedir un listado del estado del proyecto en una determinada fecha encerrando la fecha entre llaves: $ svn list --revision={2007-8-16} --verbose 1 flh 80 Aug 15 11:34 Makefile 2 flh 96 Aug 15 11:35 hola.c Pág 120 Gestión de versiones con CVS y Subversion macprogramadores.org Existen varios formatos de fecha, pero con conocer estos tres posiblemente le sea suficiente: {2007-8-16}, {2007-8-16T15:00} y {15:00}. Si no se especifica día por defecto se usa el día actual, y si no se especifica hora por defecto se usa las 00:00. Otra cosa que podemos hacer es obtener el contenido de un fichero con el comando cat: $ svn cat hola.c #include <stdio.h> int main(int argc, char* argv[]) { printf("Hola mundo controlado por Subversion\n"); return 0; } Lo interesante de este comando es que podemos combinarlo con la opción -revision para obtener el contenido de un fichero en una revisión anterior: $ svn cat --revision 1 hola.c #include <stdio.h> int main() { printf("Hola mundo controlado en Subversion"); return 0; } También podemos guardar su contenido en otro fichero haciendo una redirección: $ svn cat --revision 1 hola.c > oldhola.c Otros muchos comandos aceptan la opción --revision. Por ejemplo, para obtener el mensaje de log de la revisión 2 del fichero hola.c haríamos: $ svn log --revision 2 hola.c --------------------------------------------------------------------r2 | flh | 2007-08-15 11:35:56 +0200 (Wed, 15 Aug 2007) | 2 lines Cambio de mensaje --------------------------------------------------------------------- Y para obtener los mensajes de log del fichero hola.c entre la revisión 1 y 4 haríamos: $ svn log --revision 1:4 hola.c --------------------------------------------------------------------r1 | flh | 2007-08-15 11:34:00 +0200 (Wed, 15 Aug 2007) | 1 line Empieza el proyecto de saludos --------------------------------------------------------------------r2 | flh | 2007-08-15 11:35:56 +0200 (Wed, 15 Aug 2007) | 2 lines Cambio de mensaje --------------------------------------------------------------------- Pág 121 Gestión de versiones con CVS y Subversion macprogramadores.org También podemos actualizar el contenido de nuestra working copy a una versión o fecha anterior con el comando update y la opción --revision, por ejemplo, para obtener en la working copy el estado del repositorio en la fecha {2007-8-20} hacemos: $ svn update --revision {2007-8-20} D doc U hola.c Updated to revision 2. En este momento no debemos de modificar o hacer commit de los ficheros. Si modificamos un fichero e intentamos hacer commit, Subversion nos dará un mensaje de error: $ svn commit Sending trunk/hola.c svn: Commit failed (details follow): svn: Out of date: '/trunk/hola.c' in transaction '5-1' Para restaurar el repositorio a la versión HEAD debemos de ejecutar update sin número de revisión (o con la revisión HEAD); $ svn update C hola.c A doc A doc/README.txt Updated to revision 5. 6.6. Comparar revisiones El comando diff nos permite comparar dos revisiones de un fichero. Por defecto compara el fichero dado con su correspondiente revisión BASE (de la cual siempre hay una copia en el subdirectorio .svn). Por ejemplo: $ svn diff hola.c Index: hola.c =================================================================== --- hola.c (revision 5) +++ hola.c (working copy) @@ -3,5 +3,6 @@ int main(int argc, char* argv[]) { printf("Hola mundo controlado por Subversion\n"); + printf("Nueva linea no guardada en el repositorio\n"); return 0; } En este ejemplo hay una nueva línea en la working copy que no ha sido subida al repositorio. El comando diff también nos permite ver las diferencias entre dos revisiones cualesquiera, para lo cual usamos la opción --revision de la forma: $ svn diff --revision 1:5 hola.c Index: hola.c =================================================================== --- hola.c (revision 1) +++ hola.c (revision 5) Pág 122 Gestión de versiones con CVS y Subversion macprogramadores.org @@ -1,7 +1,7 @@ #include <stdio.h> -int main() +int main(int argc, char* argv[]) { - printf("Hola mundo controlado en Subversion"); + printf("Hola mundo controlado por Subversion\n"); return 0; } Donde estamos comparando el contenido de la revisión 1 con el contenido de la revisión 5. En concreto se nos están dando los cambios que hay que hacer para ir de la revisión 1 a la revisión 5. También podemos pedir los cambios que hay que hacer para ir de la revisión 5 a la 1 de la forma: $ svn diff --revision 5:1 hola.c Index: hola.c =================================================================== --- hola.c (revision 5) +++ hola.c (revision 1) @@ -1,7 +1,7 @@ #include <stdio.h> -int main(int argc, char* argv[]) +int main() { - printf("Hola mundo controlado por Subversion"); + printf("Hola mundo controlado en Subversion\n"); return 0; } El comando diff también puede usarse para comparar el contenido de dos ficheros distintos del repositorio (en la misma o en distinta revisión). Una forma alternativa de referirse a los ficheros y sus revisiones es usando una arroba, que es lo que se llama el peg name. Por ejemplo, si queremos ver si el contenido del fichero README.txt en la revisión 3 es el mismo que el del fichero doc/README.txt en la revisión 5 podemos hacer: $ svn diff file:///var/svn/saludos/trunk/README.txt@3 file:///var/svn/saludos/trunk/doc/README.txt@5 En este caso tenemos que usar la URL en el repositorio ya que en la working copy no tenemos el fichero README.txt, sólo el fichero doc/README.txt: $ svn diff README.txt@3 doc/README.txt@5 svn: 'README.txt@3' is not under version control 7. Ficheros binarios En un gestor de versiones es común almacenar ficheros binarios (p.e. documentación escrita con Microsoft Word, imágenes o vídeos). Subversion nos permite almacenar ficheros binarios sin problemas. Por ejemplo, para añadir un fichero Microsoft Power Pág 123 Gestión de versiones con CVS y Subversion macprogramadores.org Point llamado diseno.ppt al subdirectorio doc lo creamos usando Microsoft PowerPoint y hacemos commit del fichero como normalmente: $ svn add diseno.ppt A (bin) diseno.ppt $ svn commit diseno.ppt Adding (bin) diseno.ppt Transmitting file data . Committed revision 6. Subversion detecta que el fichero es binario automáticamente y lo marca como tal en el repositorio. Para detectar que un fichero es binario se Subversion ejecuta el comando file con la opción -i sobre el fichero, lo cual le devuelve su tipo MIME. Todos los tipos MIME que no sean de la forma text/* son considerados binarios. La principal diferencia entre los ficheros binarios y los de texto es que los ficheros binarios nunca se mezclan, sino que se guarda una versión distinta del fichero por cada revisión. 8. Conflictos En Subversion se pueden producir conflictos en dos situaciones: En el apartado 2 del Tema 2 ya indicamos que normalmente los gestores de versiones (incluido Subversion) realizan las mezclas de ficheros de texto durante la ejecución del comando update. Si el fichero del repositorio y el de la working copy han sido modificados en distintas líneas Subversion los mezclará sin problemas, pero si resultase que ambos ficheros tienen una modificación en la misma línea, diremos que se ha producido un conflicto. El otro escenario en el que se produce un conflicto es cuando un fichero binario está modificado tanto en el repositorio como en la working copy (independientemente de la parte del fichero que haya sido modificada). Subversion no sabe mezclar ficheros binarios con lo que siempre da lugar a conflictos. Por ejemplo, supongamos que dos usuarios entran en conflicto respecto al mensaje de saludo del fichero hola.c, es decir un usuario modifica este fichero, lo sube al repositorio y un segundo usuario — que no se ha bajado la revisión del repositorio — intenta subir el fichero hola.c con lo que el segundo usuario se encontraría con: $ svn commit Sending trunk/hola.c svn: Commit failed (details follow): svn: Out of date: '/trunk/hola.c' in transaction '7-1' svn: Your commit message was left in a temporary file: svn: 'svn-commit.tmp' Ahora el segundo usuario se da cuenta de que el repositorio está cambiado y de que primero tendría que haber hecho un update para comprobar si hay cambios: $ svn update C hola.c Updated to revision 7. Pág 124 Gestión de versiones con CVS y Subversion macprogramadores.org Pero al actualizar su working copy resulta que se produce un conflicto en su fichero hola.c. Cuando se produce un conflicto, Subversion crea varios ficheros en la working copy. Todos con el nombre del fichero pero con distinta extensión. En concreto para hola.c habrá creado: • • • • hola.c.r6 (la revisión 6) es el contenido de hola.c antes de modificarlo en la working copy. hola.c.r7 (la revisión 7) es el contenido de hola.c en el repositorio. Es decir lo que el otro usuario ha subido. hola.c.mime es el hola.c que teníamos en la working copy antes de actualizarnos, es decir, con las modificaciones que hiciéramos en la working copy. hola.c es el fichero hola.c modificado por update para indicar dónde se ha producido el conflicto. Esta modificación del fichero hola.c no se haría en el caso de que fuera un fichero binario. Ahora podemos ver donde está el conflicto editando el fichero hola.c: $ cat hola.c #include <stdio.h> int main(int argc, char* argv[]) { <<<<<<< .mine printf("Hola mundo del segundo usuario\n"); ======= printf("Hola mundo del primer usuario\n"); >>>>>>> .r7 return 0; En este momento debemos de contactar con el programador que modificó el fichero en el repositorio. Para saber quién ha modificado el fichero podemos usar el comando log o el comando blame: $ svn blame hola.c 1 flh #include <stdio.h> 1 flh 5 ana int main(int argc, char* argv[]) 1 flh { 7 ana printf("Hola mundo del primer usuario\n"); 1 flh return 0; 1 flh } Una vez que lleguemos a un acuerdo sobre el mensaje de saludo debemos de corregir el fichero hola.c y borrar los símbolos de conflicto: $ cat hola.c #include <stdio.h> int main(int argc, char* argv[]) { printf("Hola mundo del primer y segundo usuario\n"); return 0; } Pág 125 Gestión de versiones con CVS y Subversion macprogramadores.org Aunque hayamos corregido el conflicto, mientras que la working copy esté en estado de conflicto Subversion no nos dejará hacer commit de los cambios: $ svn commit svn: Commit failed (details follow): svn: Aborting commit: '/home/flh/saludos/trunk/hola.c' remains in conflict Para salir del estado de conflicto debemos ejecutar el comando resolved, el cual elimina los ficheros extra que se crearon durante el conflicto, y en este momento ya podemos hacer commit del fichero: $ svn resolved hola.c Resolved conflicted state of 'hola.c' $ svn commit Sending trunk/hola.c Transmitting file data . Committed revision 8. 9. Cambiar la URL de la working copy Cada directorio de la working copy tiene una URL asociada en el repositorio. Esta URL se puede ver con el comando info: $ pwd /Users/flh/saludos/trunk $ svn info Path: . URL: file:///var/svn/saludos/trunk Repository Root: file:///var/svn/saludos Repository UUID: d05e2aa6-b937-0410-8a7c-96705f58291f Revision: 7 Node Kind: directory Schedule: normal Last Changed Author: flh Last Changed Rev: 7 Last Changed Date: 2007-09-15 09:35:12 +0200 (Sat, 15 Sep 2007) Por ejemplo, aquí vemos que al subdirectorio trunk dentro del proyecto saludos le corresponde la URL file:///var/svn/saludos/trunk. Recuérdese que en el apartado 5 del Tema 7 creamos un proyecto saludos formado por tres subdirectorios: trunk, branches y tags. Esto implica que cada vez que queramos trabajar con los ficheros del subdirectorio trunk tengamos que hacer cd a este subdirectorio: $ cd $HOME $ cd saludos/trunk Si los subdirectorio branches y tags no los usamos muy a menudo podemos cambiar la URL asociada a nuestro directorio saludos para que apunte directamente a file:///var/svn/saludos/trunk, con el comando switch. De esta forma nos ahorramos un subdirectorio en nuestra vida diaria. Para ello hacemos: $ cd $HOME Pág 126 Gestión de versiones con CVS y Subversion macprogramadores.org $ cd saludos $ svn switch file:///var/svn/saludos/trunk . D trunk D branches D tags A hola.c A doc A doc/diseno.ppt A doc/README.txt A Makefile Updated to revision 8. $ ls Makefile doc/ hola.c Todavía podemos seguir trabajando con los subdirectorios tags y branches, pero para referirnos a ellos debemos de usar la URL (y no su subdirectorio en la working copy). También podemos volver a recuperar el estado anterior volviendo a ejecutar: $ svn switch file:///var/svn/saludos . Por último comentar que lo normal es ejecutar el comando switch desde dentro de la working copy y refiriéndonos sólo a URL del repositorio correspondiente, pero usando la opción --relocate también podemos cambiar a otro repositorio. 10. Tagging y branching Como indicamos en el apartado 1 del Tema 7, Subversion implementa el tagging y branching mediante copias ligeras, que vendrían a ser el equivalente a los enlaces simbólicos de UNIX, donde un fichero de tagging o branching tiene un enlace a una determinada versión de un fichero en el tronco. 10.1. Crear un tag Ya sabemos que por convenio se suelen crear tres subdirectorios en cada proyecto: trunk, tags y branches. Crear un tag en subversión es tan sencillo como ejecutar el comando copy para copiar los ficheros del trunk a un subdirectorio del directorio tags. La forma más eficiente de crear un tag es realizando la copia directamente en el repositorio, ya que evitamos transportar los ficheros del proyecto por la red. Por ejemplo, si queremos crear un tag del repositorio en el estado actual podemos hacer: $ svn copy file:///var/svn/saludos/trunk file:///var/svn/saludos/tags/ver0.1 A file:///var/svn/saludos/tags/ver0.1 Committed revision 9. Ahora se habrá creado un subdirectorio ver0.1 en el directorio tags, al cual podemos cambiar si es necesario con el comando switch: $ svn list file:///var/svn/saludos/tags ver0.1/ $ svn switch file:///var/svn/saludos/tags/ver0.1 At revision 9. Pág 127 Gestión de versiones con CVS y Subversion macprogramadores.org 10.2. Crear una rama En el apartado 4 del Tema 2 describimos cuáles eran las finalidades de las ramas. Por ejemplo permitían crear versiones experimentales, o bien sacar fuera de desarrollo una versión del software para ser testeada en una rama. Supongamos que se está evaluando la posibilidad de portar nuestro software al inglés, para lo cual se desea crear un branch donde realizar este trabajo, y decidir después si el software será finalmente portado o no. Para ello, usando el comando copy, nos creamos un branch: $ svn copy file:///var/svn/saludos/trunk file:///var/svn/saludos/branches/greetings A file:///var/svn/saludos/branches/greetings Committed revision 10. Ahora se habrá creado un subdirectorio greetings en el directorio branches, y podemos cambiar a trabajar con ese directorio con el comando switch : $ svn list file:///var/svn/saludos/branches greetings/ $ svn switch file:///var/svn/saludos/branches/greetings At revision 10. Ahora podemos traducir los mensajes al inglés y subir los cambios al repositorio: $ svn commit --message "Traduccion al ingles" Sending branches/greetings/doc/README.txt Sending branches/greetings/hola.c Transmitting file data .. Committed revision 11. Una diferencia importante entre los branches y los tags es que los branches representan múltiples revisiones, mientras que los tags representan una única revisión, con lo que normalmente nunca deberemos de modificar el contenido del directorio tags, pero como hemos visto sí que es totalmente posible modificar el contenido de una rama. 10.3. Ramas retroactivas En general conviene saber que queremos crear un branch antes de empezar a modificar el sandbox. Pero también podría ocurrir que nos demos cuenta de que lo que estamos haciendo debería haberse hecho en un branch aparte (p.e. por ser un cambio experimental) cuando ya hemos modificado el sandbox. En estos casos es posible crear un tag o branch copiando desde la working copy al repositorio. Esta opción es menos eficiente al tener que transportar los ficheros por la red, pero puede ser necesaria en caso de que, por ejemplo, tengamos ficheros modificados en la working copy y hayamos decidido no guardarlos en el tronco sino en una rama. Por ejemplo, supongamos que tenemos en la working copy nuestra propia versión de hola.c, pero la dirección del proyecto no acepta esa versión. En este caso podemos crearnos una rama para trabajar con nuestra propia versión del proyecto saludos copiando el trabajo que ya tenemos en nuestra working copy a una rama: Pág 128 Gestión de versiones con CVS y Subversion macprogramadores.org $ cat hola.c #include <stdio.h> int main(int argc, char* argv[]) { printf("Hola mundo de buenos programadores Subversion\n"); return 0; } $ svn copy . file:///var/svn/saludos/branches/otros_saludos A file:///var/svn/saludos/branches/otros_saludos Committed revision 10. 10.4. Ramas en revisiones anteriores Es también posible crear ramas que nazcan en revisiones pasadas. Para ello primero tenemos que bajar a la working copy la revisión a partir de la que queremos crear la rama, usando el comando update con --revision, y a partir de ese momento creamos la rama con los comandos: $ svn update --revision 5 U hola.c D doc/diseno.ppt Updated to revision 5. $ svn copy . file:///var/svn/saludos/branches/saludosclasicos A file:///var/svn/saludos/branches/saludosclasicos Committed revision 12. $ svn switch file:///var/svn/saludos/branches/saludosclasicos D trunk D branches D tags A hola.c A doc A doc/README.txt A Makefile Updated to revision 12. A partir de este momento ya podemos empezar a trabajar con la nueva rama. Tenga en cuenta que en Subversion no podemos borrar las revisiones que ya hayan sido guardadas en el tronco, pero podemos añadir nuevas revisiones a la rama que hemos creado. 10.5. Mezclar ramas En el apartado 4.2 del Tema 2 comentamos que existían dos formas principales de mezclar ramas: (1) Aplicar la rama al tronco, que aplicaba los cambios que había sufrido la rama (desde que se separó del tronco) al tronco. (2) Aplicar el tronco a la rama que aplicaba los cambios que había sufrido el tronco (desde que la rama se separó del tronco) a la rama. Aplicar los cambios en un sentido o en otro depende de la política que estemos siguiendo para la gestión del repositorio. Una vez aplicados estos cambios, la rama se puede abandonar, o continuar el desarrollo en función del propósito de la rama. En el apartado 4.3 y 4.4 del Tema 2 se discutieron con detalle estas políticas. Independientemente de la dirección en que se apliquen los cambios, una vez que se mezcla el tronco con una rama (o dos ramas entre sí), la rama que se aplica permanece sin cambios, y la rama sobre la que se aplica cambia. En caso de estar Pág 129 Gestión de versiones con CVS y Subversion macprogramadores.org usando ramas largas (véase apartado 4.4 del Tema 2) conviene anotar los puntos hasta donde se han cogido revisiones de la rama origen para pasarlas a la rama destino. De esta forma, cuando la rama origen evoluciona, ya no cogeremos todos los cambios desde la base de la rama, sino desde el punto donde colocamos la etiqueta. Una forma de llevar la cuenta de esta información es en los mensajes de log, tal como se explica en el apartado 10.5.1. 10.5.1. Aplicar cambios desde el tronco a la rama. Supongamos que el tronco se ha corregido un bug mientras que en paralelo en la rama (que creamos en el apartado 10.2) se está procediendo a la traducción de los mensajes al inglés. Si ahora comprobamos los mensajes de log en el tronco y en la rama veremos que estos no son los mismos. En concreto en el tronco tenemos: $ pwd /Users/flh/saludos/trunk $ svn log --------------------------------------------------------------------r13 | flh | 2007-09-15 16:30:32 +0200 (Sat, 15 Sep 2007) | 1 line Arreglado error --------------------------------------------------------------------r8 | flh | 2007-09-15 10:15:53 +0200 (Sat, 15 Sep 2007) | 2 lines Mensaje de mutuo acuerdo ················· Y en la rama tenemos: $ cd ../branches/greetings $ svn log --------------------------------------------------------------------r11 | flh | 2007-09-15 16:01:00 +0200 (Sat, 15 Sep 2007) | 1 line Traduccion al ingles --------------------------------------------------------------------r10 | flh | 2007-09-15 15:48:26 +0200 (Sat, 15 Sep 2007) | 2 lines Version traducida al ingles --------------------------------------------------------------------r8 | flh | 2007-09-15 10:15:53 +0200 (Sat, 15 Sep 2007) | 2 lines Mensaje de mutuo acuerdo ················· Los cambios del tronco no se trasladan automáticamente a la rama, sino que tendremos que aplicar los cambios del tronco a la rama. Para aplicar los cambios tenemos el comando merge, que posiblemente sea el comando más complicado de usar de Subversion, y además un cambio hecho con merge puede ser difícil de deshacer. Afortunadamente para saber los cambios que nos interesa pasar a la rama conviene observar que los comandos diff y merge pueden recibir los mismos parámetros, sólo que diff imprime los cambios entre dos revisiones mientras que merge aplica esos cambios sobre el directorio de la working copy sobre el que Pág 130 Gestión de versiones con CVS y Subversion macprogramadores.org estemos situados. Los cambios que nos interesan coger son los cambios que haya sufrido el tronco desde que creamos la rama (en nuestro ejemplo la revisión 10) hasta la HEAD del tronco. Para conocer estos cambios podemos ejecutar: $ svn diff --revision 10:HEAD file:///var/svn/saludos/trunk Index: hola.c =================================================================== --- hola.c (revision 10) +++ hola.c (revision 13) @@ -3,5 +3,5 @@ int main(int argc, char* argv[]) { printf("Hola mundo del primer y segundo usuario\n"); - return 0; + return 1; } Una vez identificados los cambios podemos situarnos en el directorio destino (en nuestro ejemplo el directorio greetings) y aplicamos los cambios con: $ svn merge --revision 10:HEAD file:///var/svn/saludos/trunk U hola.c $ cat hola.c #include <stdio.h> int main(int argc, char* argv[]) { printf("Hello world of the first and second user\n"); return 1; } Ahora el cambio del tronco ha sido transportado a nuestra rama. Si todo ha ido bien, sólo quedaría hacer un commit para que el cambio se guarde en el repositorio: $ svn commit --message "Cogidos cambios del tronco hasta esta revision" Sending greetings/hola.c Transmitting file data . Committed revision 14. Observe que hemos anotado en el mensaje de log que hemos cogido cambios hasta la revisión actual (revisión 14), con el fin de que la próxima vez que cojamos cambios del tronco cojamos sólo los cambios de la revisión 14 en adelante. 10.5.2. Aplicar cambios de la rama al tronco Supongamos ahora que el traductor de la rama ha terminado de traducirla, y ha enviado los cambios en la rama al proyecto del repositorio. Además, el consejo de dirección se ha reunido y ha decidido aprobar la traducción del programa al inglés. Luego, es hora de pasar los cambios que el traductor hizo en la rama al tronco. Para ello, primero comprobamos en qué consisten estos cambios con el comando diff : $ svn diff --revision 10:HEAD file:///var/svn/saludos/branches/greetings Index: hola.c =================================================================== Pág 131 Gestión de versiones con CVS y Subversion macprogramadores.org --- hola.c (revision 10) +++ hola.c (revision 14) @@ -2,6 +2,6 @@ int main(int argc, char* argv[]) { - printf("Hola mundo del primer y segundo usuario\n"); - return 0; + printf("Hello world of the first and second user\n"); + return 1; } Index: doc/README.txt =================================================================== --- doc/README.txt (revision 10) +++ doc/README.txt (revision 14) @@ -1 +1 @@ -Fichero de documentacion general +Overview documentation file Y para aplicar los cambios nos situamos en la working copy del tronco y ejecutamos el comando: $ svn merge --revision 10:HEAD file:///var/svn/saludos/branches/greetings U hola.c U doc/README.txt Ya sólo queda hacer un commit de los cambios en la working copy del tronco con el comando: $ svn commit --message "Traducido al ingles el tronco. Para ello hemos traido el contenido de la rama branch/greetings" Sending trunk/doc/README.txt Sending trunk/hola.c Transmitting file data .. Committed revision 15. De nuevo, conviene etiquetar la rama por si en el futuro queremos pasar más revisiones de la rama al tronco, saber hasta que revisión hemos pasado. 10.5.3. Aplicar cambios entre ramas Aunque lo normal es aplicar cambios desde el tronco a una rama, o desde una rama al tronco. También es posible aplicar cambios entre dos ramas. La forma de proceder es similar: Nos situamos en la rama destino y ejecutamos el comando merge indicando el rango de revisiones de la rama origen a aplicar sobre nuestro directorio de la working copy. 10.6. Deshacer la aplicación de una rama En el apartado 6.6 del Tema 8 comentamos que podíamos usar el comando diff para obtener los cambios entre números de revisiones hacia atrás (backward). Esta forma de trabajar también se puede usar con el comando merge para deshacer un cambio. Por ejemplo, supongamos que el consejo de dirección de nuestra empresa cambia, y pasa a dirigirlo un alto cargo de la Real Academia Española que decide que Pág 132 Gestión de versiones con CVS y Subversion macprogramadores.org vamos a seguir distribuyendo nuestra aplicación en español. Podemos situarnos en la working copy del tronco y deshacer los cambios hechos por la rama con el comando: $ svn merge --revision 15:14 file:///var/svn/saludos/trunk U hola.c U doc/README.txt Que volvería a dejar los ficheros en español. Por último necesitamos subir los cambios al repositorio: $ svn commit --message "Volvemos al espanol" Sending trunk/doc/README.txt Sending trunk/hola.c Transmitting file data .. Committed revision 16. Obsérvese que no hemos borrado la revisión 15, sino que hemos creado otra revisión donde los ficheros de nuevo están en español. 11. Propiedades Además de almacenar un log de los cambios entre versiones, Subversion también permite asociar metadatos a cada fichero, directorio y revisión, para lo cual podemos usar propiedades, que son pares clave=valor. Las propiedades pueden variar en cada revisión y se almacenan en el proyecto del repositorio al hacer commit. Existen dos tipos de propiedades, las propiedades de fichero las cuales se asocian a ficheros y directorios de una determinada revisión, y las propiedades de revisión, las cuales se asocian a todos los ficheros de una determinada revisión. En cualquier caso las propiedades pueden ser distintas en cada revisión, y su valor se hereda entre revisiones sucesivas si no se cambia. 11.1. Guardar metadatos en propiedades Podemos asociar propiedades con ficheros y directorios usando el comando propset. El formato del comando propset es: svn propset key value file Como es habitual en Bash, si un parámetro tiene espacios debemos de entrecomillarlo. Por ejemplo: $ svn propset responsable "Fernando Lopez" hola.c property 'responsable' set on 'hola.c' La clave de la propiedad no puede tener espacios ni empezar por svn:, ya que este prefijo se reserva para las propiedades de Subversion. El valor de una propiedad puede ser tanto texto como binario. Si el valor de la propiedad es un texto corto, podemos usar la forma que hemos visto, pero si es un texto largo o un valor binario debemos usar la forma: svn propset key --file property_file file Pág 133 Gestión de versiones con CVS y Subversion macprogramadores.org Donde property_file es el fichero con el valor que queremos fijar para la propiedad key del fichero file. Por ejemplo: $ svn propset cambios --file cambios.txt hola.c property 'cambios' set on 'hola.c' 11.1.1. Editar propiedades Si en vez de cambiar todo el valor de una propiedad, sólo queremos modificar una parte, podemos usar el comando propedit el cual abre el editor por defecto y nos permite editar la propiedad. Por ejemplo, para editar la propiedad anterior podemos hacer: $ svn propedit cambios hola.c Si la propiedad no existiera Subversion crea una nueva propiedad con la clave dada. Tenga en cuenta que tanto propset como propedit modifican la propiedad en la working copy, pero para guardar la propiedad en el repositorio deberemos ejecutar el comando commit. Una vez que una propiedad se fija, está se va pasando entre versiones hasta que otra versión la cambia. Por ejemplo: $ svn propget --revision 14 responsable hola.c Fernando Lopez $ svn propget --revision 16 responsable hola.c Ana Martinez 11.1.2. Propiedades de revisión Las propiedades de revisión se asignan a todos los ficheros de una revisión. Por defecto la modificación de propiedades de revisión está desactivada, y no se recomienda su midificación. 11.1.3. Fijar propiedades automáticamente Si queremos que una propiedad se fije para todos los ficheros de un determinado tipo que se añadan al proyecto podemos pedir a Subversion que lo haga automáticamente para todos los ficheros que cumplan con un determinado patrón en un fichero de configuración de Subversión tal como se explica en el apartado 1.1 del Tema 9. 11.2. Leer metadatos de las propiedades Las propiedades se pueden leer con el comando propget que recibe la clave de la propiedad e imprime su valor, Por ejemplo: $ svn propget responsable hola.c Fernando Lopez También podemos pasar varios ficheros a propget y nos devuelve un listado ficherovalor separados por un guión para facilitar su lectura: $ svn propget responsable * Makefile - Fernando Lopez Pág 134 Gestión de versiones con CVS y Subversion macprogramadores.org doc - Ana Martinez hola.c - Fernando Lopez Por cada valor, Subversion añade un final de línea para facilitar la lectura. Podemos usar la opción --strict para desactivar este añadido, lo cual es útil para obtener propiedades binarias. Por ejemplo: $ svn propget --strict thumbnail demo.mpg > demo.jpg 11.2.1. Listar las propiedades Podemos obtener un listado de las claves de las propiedades asociadas a uno o más ficheros con el comando proplist: $ svn proplist hola.c Properties on 'hola.c': descripcion obj responsable Podemos usar la opción --verbose para obtener también los valores de las propiedades: $ svn proplist --verbose hola.c Properties on 'hola.c': descripcion : Fichero donde se guardan mensajes de saludo obj : ELF responsable : Fernando Lopez 11.2.2. Obtener propiedades de revisión En general Subversion no recomienda modificar las propiedades de revisión, pero sí que permite leerlas con el comando propget y la opción --revprop. Además es necesario indicar la revisión que queremos leer con la opción --revision. En caso de querer la última revisión en el repositorio podemos usar HEAD. Por ejemplo: $ svn propget --revprop --revision HEAD svn:log hola.c Arreglado error 11.3. Borrar propiedades Para borrar propiedades tenemos el comando propdel que recibe el nombre de la propiedad a borrar y el nombre del fichero al que está asociada la propiedad. Por ejemplo: $ svn propdel descripcion hola.c property 'descripcion' deleted from 'hola.c'. 11.4. Propiedades del sistema Subversion proporciona un número de propiedades del sistema cuyo nombre empieza por svn: que tienen un significado especial para Subversion. Estas propiedades se pueden dividir en dos categorías: Propiedades de fichero y Pág 135 Gestión de versiones con CVS y Subversion macprogramadores.org propiedades de revisión. Algunas de estas propiedades son fijadas por el usuario para cambiar la funcionalidad de Subversion, y otras son fijadas automáticamente por Subversion. 11.4.1. Propiedades de fichero Las únicas propiedades de fichero que son fijadas automáticamente por Subversión son svn:executable y svn:mime-type, las demás deben de ser fijadas por el usuario. La propiedad svn:eol-style Por defecto Subversión nunca modifica el contenido de los ficheros, pero podemos pedirle que cambie los finales de línea de los ficheros de texto (los que tengan un tipo MIME de la forma text/*) a un determinado formato usando la propiedad svn:eol-style. En concreto, al bajar el fichero a la working copy se le cambian los finales de línea para que cumplan con el valor dado, y al subirlos no se modifica el final de línea con el que se han guardado. Esta propiedad puede tomar los valores: native hace que al bajar un fichero del repositorio a la working copy se le asigne el final de línea propio del sistema donde está la working copy: CRLF para Windows, CR para Mac OS Classic y LF para Mac OS X y UNIX. CR hace que al bajar un fichero del repositorio a la working copy se le asigne siempre el final de línea CR. LF hace que al bajar un fichero del repositorio a la working copy se le asigne siempre el final de línea LF. CRLF hace que al bajar un fichero del repositorio a la working copy se le asigne siempre el final de línea CRLF. Desafortunadamente la propiedad svn:eol-style tiene que ser fijada para cada fichero (y no, por ejemplo, a nivel de directorio). En el apartado 1.1 del Tema 9 veremos cómo hacer esta asignación a cada fichero automáticamente. La propiedad svn:executable En sistemas UNIX es útil que se guarde el bit de permiso de ejecución para los ficheros ejecutables. Cuando añadimos un fichero al proyecto (con el comando add) si el fichero tiene permiso de ejecución, se crea la propiedad svn:executable en el fichero automáticamente. Cualquier valor de la propiedad es válido para que el fichero sea ejecutable. Subversion por defecto usa cómo valor de está propiedad "*". Nosotros también podemos cambiar esta propiedad a posteriori sobre un fichero que queramos que tenga permiso de ejecución. $ svn propset svn:executable "*" limpia.sh property 'svn:executable' set on ' limpia.sh' En sistemas Windows esta propiedad se puede fijar pero no tiene efecto. Pág 136 Gestión de versiones con CVS y Subversion macprogramadores.org La propiedad svn:externals Muchas veces un proyecto usa librerías de otro proyecto. P.e. nosotros podemos tener un proyecto con una aplicación de dibujo que usa una librería de cálculo matricial. Cuando existen estas dependencias nos gustaría que al actualizarse la librería los cambios en la librería se transportases automáticamente a nuestra aplicación de dibujo. En este caso podemos crear en nuestro proyecto una referencia externa al otro proyecto (o a un subdirectorio del otro proyecto). También es posible crear referencias dentro de nuestro propio proyecto a subdirectorios de nuestro propio proyecto (tal como hace UNIX con los enlaces simbólicos), de esta forma evitamos tener duplicados ficheros del proyecto, y cuando uno se actualiza se actualizan los demás. Por ejemplo, vamos a hacer que el tronco del proyecto despidos pase a ser un subdirectorio exportado en el tronco del proyecto saludos. Además vamos a hacer que dentro del tronco del proyecto saludos haya una referencia al directorio doc llamado ayuda. Para ello tenemos que crear una propiedad como la siguiente en el directorio trunk del proyecto saludos: $ pwd /Users/flh/saludos/trunk $ svn propget svn:externals . despidos file:///var/svn/despidos/trunk ayuda file:///var/svn/saludos/trunk/doc Como la propiedad puede tener más de una línea, debemos crearla con propedit. En cada línea aparece primero el nombre del subdirectorio y luego la referencia al proyecto (o parte de proyecto que queremos referenciar). Para bajarnos los cambios debemos de ejecutar el comando update : $ svn update Fetching external item into 'despidos' A despidos/adios.c A despidos/Makefile Updated external to revision 1. Fetching external item into 'ayuda' A ayuda/diseno.ppt A ayuda/README.txt Updated external to revision 13. En ocasiones los cambios en un proyecto externo pueden hacer que nuestro proyecto deje de funcionar. En este caso es recomendable referenciar una determinada versión del proyecto externo de la forma: $ svn propget svn:externals . despidos --revision 34 file:///var/svn/despidos/trunk ayuda file:///var/svn/saludos/trunk/doc El comando status muestra una X en el caso de las referencias externas: $ svn status X ayuda X despidos El soporte para referencias externas en Subversion tiene varias limitaciones. Primero, las referencias externas sólo pueden apuntar a subdirectorios (no a ficheros). Segundo, la referencia no puede tener direcciones relativas (p.e. ../../midoc). Pág 137 Gestión de versiones con CVS y Subversion macprogramadores.org Tercero, no debemos de modificar los ficheros exportados por referencias externas, y si los actualizamos su contenido no se sincroniza con el proyecto referenciado. Por ejemplo, en el caso de la documentación de nuestro proyecto saludos, siempre debemos actualizar los ficheros del subdirectorio doc, y no los del subdirectorio ayuda. Los cambios en los ficheros del subdirectorio doc sí que se propagan a los ficheros del subdirectorio ayuda. En ocasiones las referencias externas tienen la forma svn+ssh://. En este caso no debemos de incluir un nombre del usuario en la referencia externa, es decir, no debemos de hacer cosas como: ayuda svn+ssh://[email protected]/var/svn/saludos/trunk/doc Ya que entonces a otros usuarios (distintos a flh ) se les pedirá el password de flh, y no podrán bajarse la referencia externa. La forma correcta de declarar la anterior referencia externa deberá tener la forma: ayuda svn+ssh://dymas.ii.uam.es/var/svn/saludos/trunk/doc Ahora SSH usará el nombre de usuario en la máquina local para logarse en la máquina remota. Si el nombre del usuario en la máquina local y remota no coinciden (p.e. en la máquina local tenemos el usuario fernando y en la remota flh ), podemos usar la variable de entorno SVN_SSH para indicar a SSH parámetros como el nombre de usuario con el que logarse en la máquina remota, es decir, podemos hacer: $ export SVN_SSH=flh Ahora, cuando Subversion se logue en la máquina remota usará el nombre flh, y las referencias externas se resolverán adecuadamente. La propiedad svn:ignore La working copy tiende a llenarse de ficheros que no queremos subir al repositorio, como, por ejemplo, ficheros de código objeto, ejecutables o ficheros de proyecto del entorno de desarrollo que estamos usando. El comando status muestra estos ficheros con una ?. Si queremos evitar que aparezcan estos ficheros podemos fijar la propiedad svn:ignore con los nombres de los ficheros y directorios a ignorar. También podemos usar patrones. Por ejemplo, para evitar que aparezcan los ficheros *.o y el fichero a.out podemos fijar la propiedad svn:ignore en el directorio raíz: $ svn propget svn:ignore *.o a.out . Por desgracia svn:ignore es no recursivo, con lo que tenemos que fijarlo por cada directorio donde haya ficheros que queramos ignorar. En el apartado 1.1 del Tema 9 veremos cómo ignorar de forma recursiva a nivel de usuario. La propiedad svn:mime-type Pág 138 Gestión de versiones con CVS y Subversion macprogramadores.org En el apartado 7 comentamos que Subversion usaba el tipo MIME para determinar si un fichero es binario o no. Los ficheros cuyo tipo MIME no empiece por text/* se consideran binarios. Sólo en caso de que un fichero sea considerado binario, Subversion incluirá automáticamente su tipo MIME en la propiedad svn:mime-type . Normalmente el mecanismo de detección de ficheros binarios funciona bien, con lo que nosotros no tendremos que hacer nada. Pero en caso de que el mecanismo de detección de ficheros binarios esté fallando, nosotros podemos fijar la propiedad svn:mime-type manualmente. 11.4.2. Propiedades de revisión Cuando se hace commit de una revisión Subversion automáticamente fija tres propiedades de revisión: • • • svn:autor con el nombre del usuario que ha creado la revisión. svn:date con la fecha en que se creó la revisión svn:log con le mensaje de log de la revisión Normalmente nosotros no deberemos modificar estás propiedades, aunque las podemos leer. Pág 139 Gestión de versiones con CVS y Subversion macprogramadores.org Tema 9 Subversion desde el punto de vista del administrador Sinopsis: En este último tema trataremos aspectos de los cuales sólo suele ocuparse el administrador del repositorio. Empezaremos viendo qué ficheros de configuración existen en Subversion, para luego ver cómo controlar el acceso al repositorio, cómo hacer backups del repositorio y cómo programar hook scripts en el repositorio. Pág 140 Gestión de versiones con CVS y Subversion 1. macprogramadores.org Los ficheros de configuración Subversion tiene una serie de funcionalidades cuyo comportamiento se puede personalizar. Para ello Subversion utiliza ficheros de configuración. Estos ficheros de configuración se pueden dividir en ficheros de configuración a nivel de sistema y ficheros de configuración a nivel de usuario. Los ficheros de configuración a nivel de usuario se guardan en el subdirectorio .subversion dentro del directorio home. Los ficheros de configuración a nivel de sistema se guardan en el directorio /etc/subversion. Por defecto el directorio /etc/subversion no existe. El administrador puede crearlo copiando su subdirectorio .subversion en el directorio /etc/subversion. Las opciones a nivel de sistema se aplican siempre que el usuario no haya creado una opción en su subdirectorio que la modifique. El subdirectorio .subversion se crea la primera vez que ejecutamos un comando de Subversion y se crea con dos ficheros llamados config y server. Estos ficheros contienen opciones que vamos a estudiar el los siguientes apartados. Los valores por defecto son razonables en la mayoría de los casos. Además normalmente existe un subdirectorio auth con una caché de las credenciales de identificación del usuario. El contenido de auth no está pensado para ser editado directamente, pero si tiene problemas de autentificación ante un servidor a veces desaparecen si borramos este subdirectorio y dejamos que Subversion lo vuelva a crear. En los siguientes apartados vamos a estudiar las principales opciones de los ficheros config y server. 1.1. El fichero config El fichero config consta de cinco secciones: [auth], [helpers], [tunnels], [miscellany] y [auto-props]. Vamos a ir viendo qué contiene cada sección. La sección [auth] tiene sólo dos opciones: store-password que por defecto está a yes e indica si queremos que Subversion guarde nuestro password en caché. Si quiere aumentar la seguridad de su cuenta ponga esta opción a no, pero se le pedirá el password en cada conexión al repositorio, lo cual puede resultar tedioso. La otra opción es store-auth-creds que también está por defecto a yes, pero puede ponerla a no si quiere que no se haga caché de su nombre de usuario y certificados SSL. La sección [helpers] permite indicar qué comandos externos puede usar Subversion. La opción editor-cmd permite indicar el editor por defecto, y la opción diff-cmd permite indicar el comando de diff a usar por Subversion. Una opción bastante útil que encontramos en la sección [miscellany] es la opción global-ignores la cual contiene una lista de patrones, separados por espacio, de los ficheros que queremos que Subversion ignore durante la ejecución del comando status. Por ejemplo: global-ignores = *.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#* .DS_Store Esta opción está por defecto desactivada, le recomendamos activarla. Subversion usa por defecto UTF-8 para almacenar los mensajes de log. Si nuestro editor utiliza otra codificación podemos usar la opción log-encoding para indicar la codificación a usar. Por ejemplo joe usa latin1, con lo que si usamos joe deberíamos activar: Pág 141 Gestión de versiones con CVS y Subversion macprogramadores.org log-encoding = latin1 La opción enable-auto-props que por defecto está desactivada permite activar las auto-props, que Subversion automáticamente añade a los ficheros en función de su nombre. En la sección [auto-props] encontramos los siguientes ejemplos de auto-props: *.c = svn:eol-style=native *.cpp = svn:eol-style=native *.h = svn:eol-style=native *.dsp = svn:eol-style=CRLF *.dsw = svn:eol-style=CRLF *.sh = svn:eol-style=native;svn:executable *.txt = svn:eol-style=native *.png = svn:mime-type=image/png *.jpg = svn:mime-type=image/jpeg Makefile = svn:eol-style=native 1.2. El fichero servers En la mayoría de los casos conectarse a un repositorio Subversion no requiere ninguna modificación en la configuración. Sin embargo cuando nos conectamos a repositorios usando HTTP, y especialmente si además usan SSL para establecer una conexión segura, necesitamos modificar la configuración de este fichero. El fichero servers consta de una sección [global] con opciones globales de conexión. Si queremos personalizar la conexión en función del servidor donde está el repositorio Subversion al que conectarnos también podemos crear grupos con la sección [groups], de forma que las opciones sólo afectan a los servidores de ese grupo. Existe un conjunto de opciones destinadas a poder conectar con un servidor HTTP o HTTPS cuando el cliente está dentro de una red donde sólo puede salir al exterior a través de un proxy. Estas opciones son más o menos autoexplicativas y tienen la forma: http-proxy-host = defaultproxy.whatever.com http-proxy-port = 7000 http-proxy-username = defaultusername http-proxy-password = defaultpassword http-proxy-exceptions = *.exception.com, www.internal-site.org http-compression = no http-timeout = 30 Un segundo conjunto de opciones permiten resolver problemas cuando el servidor SSL no nos acepta. Estas opciones tienen la forma: neon-debug-mask = 130 ssl-trust-default-ca = yes ssl-authority-files = /path/to/CAcert.pem;/path/to/CAcert2.pem La opción neon-debug-mask es bastante útil cuando hay problemas al conectar a un servidor Subversion a través de WEB-DAV porque hace que se muestre una traza por Pág 142 Gestión de versiones con CVS y Subversion macprogramadores.org consola de los comandos que se intercambian cliente y servidor, lo cual suele ayudar a identificar errores. La librería neon es una librería del proyecto Apache usada por Subversion para conectarse a servidores WEB-DAV. Poniendo la opción ssl-trustdefault-ca a yes hacemos que Subversion confíe en los CAs que trae por defecto OpenSSL. La opción ssl-authority-files permite indicar CAs adiciones en los que confiar. Si nuestro servidor requiere que el cliente se identifique deberemos de indicar nuestro fichero con el certificado de cliente en la opción ssl-client-certfile. Si nuestro certificado de cliente está encriptado podemos indicar el password con el que está encriptado en la opción ssl-client-cert-password. Esta última opción es poco segura y quizá sea mejor dejar que Subversion nos pregunte el password con el que abrir el certificado de usuario. 2. Control de acceso al repositorio En el apartado 7 del Tema 7 vimos que existían varios esquemas para las URL de conexión al repositorio: file:/// para acceso al sistema de ficheros de la máquina local, svn:// para conexión con el demonio svnserve, svn+ssh:// para conexión a través de SSH, y http:// o https:// para conexión a través de WEB-DAV. La política de seguridad en las conexiones a través de file:/// es la misma que la política de seguridad del sistema de ficheros donde estemos trabajando. La política de seguridad a través de svn+ssh:// es la misma que la de SSH, es decir, necesitamos tener una cuenta de SSH en la máquina donde está el repositorio. La política de seguridad en las conexiones de tipo http:// o https:// se gestiona con los ficheros de configuración de Apache y no la vamos a tratar en este documento. Nos queda por conocer la política de seguridad en el esquema svn:// que es la que vamos a tratar en el resto de este apartado. En este último caso el acceso a Subversion se configura en el fichero svnserve.conf. Este fichero se encuentra dentro del subdirectorio conf de cada repositorio, con lo que si tenemos varios repositorios en una máquina las reglas de acceso a cada repositorio pueden ser distintas. Por defecto todo el mundo tiene acceso anónimo a través de svn:// a un repositorio. Si queremos deshabilitar el acceso anónimo debemos activar la propiedad: anon-access = none Esta propiedad también se puede poner a read para que los usuarios anónimos sólo tengan acceso de lectura o a write para que los usuarios anónimos tengan permiso de lectura y escritura. Si queremos controlar el acceso mediante cuentas de usuario, podemos crear cuentas activando la propiedad: password-db = passwd Esta propiedad apunta al fichero passwd, que también está en el subdirectorio conf, y el cual contiene nombres y passwords de usuarios de la forma: [users] harry = harryssecret sally = sallyssecret Pág 143 Gestión de versiones con CVS y Subversion macprogramadores.org En caso de que nuestro repositorio aloje varios proyectos (véase sección 3 del Tema 8), todavía podemos definir con más detalle los accesos a cada parte del repositorio usando el fichero authz. Para utilizar este fichero primero debemos de activar en svnserve.conf la propiedad: authz-db = authz 3. Backup del repositorio Si hacemos un backup del repositorio usando los comandos de copia del sistema operativo la copia del repositorio se podría corromper si alguien accede al repositorio durante el backup. En caso de estar usando un repositorio Berkeley DB, la copia podría verse dañada en su totalidad. En caso de estar usando FSFS sólo se dañarían los ficheros de la copia que se estaban modificando durante el backup. Para evitar este problema existen dos opciones, una es bloquear el acceso al servidor mientras se está haciendo la copia de seguridad, y la otra es usar el comando svnadmin hotcopy. Usar este comando es tan sencillo como hacer: $ svnadmin hotcopy /var/svn/saludos /mnt/backup/saludos $ svnadmin hotcopy /var/svn/despidos /mnt/backup/despidos Este comando se debe de ejecutar una vez por cada repositorio. En caso de que haya problemas con el repositorio, recuperar la copia de seguridad es tan sencillo como parar el servidor y copiar los ficheros desde el directorio de backup al directorio del repositorio. 4. Hook scripts Subversion permite que cuando un determinado evento ocurra en el repositorio se ejecute un script. Normalmente estos scripts son scripts Bash, pero pueden también estar escritos en otros lenguajes de script como Python o Perl, o bien ser ejecutables binarios. 4.1. Hook scripts disponibles Cuando ejecutamos el comando svnadmin create, se crean varias plantillas de scripts en el repositorio, en concreto en el subdirectorio hooks del repositorio. En este apartado vamos a comentar cómo funcionan estos scripts. Los scripts reciben argumentos de línea de comandos. Cuando alguno de estos scripts devuelve un valor distinto de cero, Subversion rechaza la ejecución de la operación. La excepción a esta última regla son los scripts post-commit y post-revprop-change, ya que estos se ejecutan después de haberse llevado a cabo la operación, y ésta ya no se puede deshacer. start-commit lo ejecuta Subversion cuando un cliente hace un commit. El script lo ejecuta Subversion antes de empezar una transacción. Como argumento recibe el path del repositorio y el nombre del usuario. Si el script devuelve un código de terminación distinto de cero la operación de commit falla. Pág 144 Gestión de versiones con CVS y Subversion macprogramadores.org pre-commit lo ejecuta Subversion después de crear una transacción. El script puede usar la transacción para leer el contenido de los ficheros de los que se está haciendo commit y decidir si se acepta el commit. Si el script devuelve un código de terminación distinto de cero la transacción se deshace y la operación de commit falla. post-commit lo llama Subversion después de terminar la transacción. La transacción ya no se puede deshacer, pero el script puede apuntar que la transacción se ha hecho y realizar alguna operación de log. pre-revprop-change lo llama Subversion antes de modificar una propiedad de revisión. Para que el cliente pueda modificar propiedades de revisión es necesario que este script exista, y que devuelva el código de terminación cero. El script recibe como argumentos de línea de comando el path del repositorio, la propiedad que se quiere cambiar y el nombre del usuario que la está queriendo cambiar. post-revprop-change lo llama Subversion después de cambiar una propiedad de revisión para notificar que la propiedad se ha cambiado. 4.2. Qué puede y qué no puede hacer un hook script Los hook script pueden examinar el contenido del repositorio, pero no deben de modificarlo, con lo que en vez de usar el comando svn deben de usar el comando svnlook, el cual es parecido a svn pero no modifica el estado del repositorio, y sólo puede ser ejecutado en local dentro del servidor. En caso de querer acceder al contenido de una transacción (p.e. pre-commit recibe como argumento un identificador de transacción) este identificador de transacción se pasa a svnlook con la opción --transaction que recibe el identificador de transacción (de forma similar a como --revision recibe el número de revisión). Los hook scripts también pueden devolver un mensaje de error al cliente escribiendo en la salida de errores estándar. Este mensaje sólo se devuelve al cliente si el hook script falla, si el hook script devuelve el código de terminación cero no hay forma de enviar un mensaje al cliente. Por ejemplo, el siguiente contenido en pre-commit guardaría el mensaje de log en /tmp/log.txt y devolvería un mensaje de error al cliente indicando que el repositorio está cerrado. REPOS="$1" TXN="$2" SVNLOOK=/usr/bin/svnlook $SVNLOOK log --transaction "$TXN" "$REPOS" > /tmp/log.txt echo "El repositorio esta cerrado." >&2 echo "Su mensaje de log se ha guardado en /tmp/log.txt" >&2 exit 1 Los hook scripts no deben modificar el contenido de la transacción ni el contenido del repositorio. Es muy común el plantearse la posibilidad de hacer un script que modifique el contenido que se está enviando al repositorio, pero esto no debe de hacerse. Lo que podemos hacer es denegar el commit si este contenido no es adecuado. La razón por la que no debemos modificar nunca el contenido de lo que se está enviando al repositorio es porque el cliente quedaría desincronizado con respecto a lo que hemos guardado en el repositorio. Pág 145 Gestión de versiones con CVS y Subversion 4.3. macprogramadores.org Comprobar la correcta indentación de los ficheros El ejemplo más típico de hook script es el fichero pre-commit, el cual se ejecuta antes de aceptar el commit de un fichero. Vamos a crear un script de ejemplo que valida que el fichero esté indentado de acuerdo a las reglas de indentado del proyecto. El comando indent permite indentar un código fuente C de acuerdo a varias reglas de indentado. Si le pasamos un fichero y la opción –gnu, indenta el fichero de acuerdo a las reglas de indentado de GNU. Por ejemplo: $ cat hola.c #include <stdio.h> int main(int argc, char* argv[]) { printf("Hola mundo del primer y segundo usuario\n"); return 1; } $ cat hola.c | indent -gnu #include <stdio.h> int main (int argc, char *argv[]) { printf ("Hola mundo del primer y segundo usuario\n"); return 1; } El Listado 6.1 muestra un pequeño script que sirve para comprobar si los ficheros que le pasamos como parámetro están indentados de acuerdo a una determinada regla de indentado. #!/bin/bash REPOS="$1" TXN="$2" SVNLOOK=/usr/bin/svnlook # Procesa cada directorio cambiado DIRS=$("$SVNLOOK" dirs-changed "$REPOS") for D in $DIRS do # Procesa cada fichero cambiado FICHEROS=$($SVNLOOK changed "$REPOS" "$D") for F in $FICHEROS do case $F in *.c|*.h) F_TMP1="/tmp/id.1.$$" F_TMP2="/tmp/id.2.$$" $SVNLOOK cat --transaction $TXN $REPOS $F > $F_TMP1 $SVNLOOK cat --transaction $TXN $REPOS $F \ | indent -gnu > $F_TMP2 diff $F_TMP1 $F_TMP2 >/dev/null 2>/dev/null if [ $? -ne 0 ]; then echo "El fichero $F no esta bien indentado" >&2 exit 1 fi;; esac done Pág 146 Gestión de versiones con CVS y Subversion macprogramadores.org done exit 0 Listado 6.2: Script pre-commit Para que este hook script pueda ejecutarse debe activar el bit de ejecución: $ chmod +x /var/svn/saludos/hooks/pre-commit Si el código de terminación es 0 es que todos los ficheros modificados están bien indentados. Si el código de terminación es 1 es que algún fichero modificado está mal indentado. Si ahora el usuario intenta hacer commit de un fichero que no está correctamente indentado el commit falla: $ svn commit Sending hola.c Transmitting file data .svn: Commit failed (details follow): svn: 'pre-commit' hook failed with error output: El fichero hola.c no esta bien indentado Pág 147