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

Documentos relacionados