Primero nos bajamos el proyecto que creamos antes, o algún otro que encontremos por ejemplo en http://svn.apache.org/repos/asf/. Yo bajé el que cree antes:
# creamos un directorio para albergar el proyecto mkdir /home/jano/awpool # nos situamos en él cd /home/jano/awpool # y bajamos el proyecto svn checkout http://127.0.0.1:80/repos/awpool/trunk .
Esta copia local contiene un subdirectorio svn en cada directorio, lo que permite que sea gestionada con el cliente de Subversion.
Si queremos actualizar nuestra copia local con los cambios que otros usuarios hayan enviado al repositorio usamos update:
# por defecto es recursivo svn update At revision 4. # para actualizar solo un directorio svn -N update At revision 4. # para un fichero especifico svn update GenericsTest.java At revision 2.
Acabo de modificar un fichero en local, y voy a actualizarlo para luego hacer commit (no quiero machacar los cambios de nadie). Pero al actualizar Subversion muestra una C de conflicto junto al fichero:
debian:/# svn update . C GenericsTest.java Updated to revision 5.
Además han aparecido varias versiones del fichero en disco:
debian:/# ls GenericsTest.java # el fichero con mis cambios y marcas de conflicto GenericsTest.java.mine # el fichero con mis cambios GenericsTest.java.r4 # la revisión antes de mis cambios GenericsTest.java.r5 # la última revisión que acabo de actualizar
Todo esto ocurre porque desde la última vez que bajé este fichero, alguién más lo cambio, y al actualizarme con el servidor he recibido un trozo de código que entra en conflicto con mis cambios. O dicho de otra manera, un cambio que de haberse aplicado sobreescribiría mis propios cambios, y yo perdería información. Afortunadamente siempre podemos actualizar sin miedo, porque Subversion suspende la fusión de cambios y nos advierte del conflicto.
Vamos a abrir el fichero para ver que aspecto tienen las "marcas de conflicto":
private static void print(Type t) { if (t instanceof TypeVariable) { print((TypeVariable)t); <<<<<<< .mine } else if (t instanceof WildcardType) { print((ParameterizedType)t); ======= } else if (t instanceof WildcardType) { print((WildcardType)t); >>>>>>> .r2 } else { System.out.println(t); } }
Observa que hay un trozo <<< .mine mi-código === y un trozo === código-revisión-2 >>> .r2. Se corresponden a mi código y al código de la revisión 2 que acabo de bajarme.
Para solucionar el conflicto puedo:
Descartar mis cambios locales, o los cambios que baje del servidor. Esto se hace sobreescribiendo el fichero original GenericsTest.java por alguna de las copias que han aparecido.
Solucionar el conflicto editando a mano el fichero:
private static void print(Type t) { if (t instanceof TypeVariable) { print((TypeVariable)t); } else if (t instanceof WildcardType) { print((WildcardType)t); } else { System.out.println(t); } }Oops! el cambio correcto no era el mío, asi que he dejado lo que ya había.
Tras solucionar el conflicto elimino los ficheros sobrantes y hago commit:
rm *.mine *.r4 *.r5 svn ci GenericsTest.java
Como ves, normalmente se soluciona intuitivamente editando el fichero a mano. Si quieres más detalles consulta el manual de Subversion: http://svnbook.red-bean.com/html-chunk/ch03s05.html#svn-ch-3-sect-4.4.
Al contrario que con CVS, los commit son atómicos. Es decir, cuando subes varios ficheros llegan en bloque (todos o ninguno), y si dos desarrolladores suben sus cambios a la vez primero se graba todo el bloque de uno, y luego el del otro. Los cambios nunca se mezclan.
Es buena práctica pensar por adelantado en el mensaje de log que vamos a usar, y hacer commits de cambios relacionados. De ese modo es más fácil revisar los cambios buscando errores. Conviene definir una variable SVN_EDITOR=kedit (o SVN_EDITOR=notepad) para usarlo como editor por defecto. De no hacerlo, tendremos que añadir el flag -m 'mensaje' para el obligado comentario que debe acompañar todo commmit.
Para enviar modificaciones locales al servidor:
# envía las modificaciones encontradas en todos los subdirectorios $svn commit # actualiza todos los *.txt del directorio actual y lo que haya en el directorio src $svn commit *.txt srcEl cliente svn lanzará el editor configurado, escribimos el mensaje de log, grabamos, cerramos el editor, y el commit se realiza. Un ejemplo de la salida en Windows:
$svn commit *.txt src Sending STATUS.txt Sending src/java.head/overview.html Transmitting file data .. Committed revision 4.
Si alguno de los ficheros que enviamos ha sido modificado en el repositorio, es obligado que actualicemos dichos cambios (usando update) antes de hacer commit. Esto evita que machaquemos los cambios de otros usuarios.
El comando status muestra las modificaciones que hemos hecho en la copia local desde el último checkout o commit. Es útil hacerlo antes de un commit para hacernos idea de lo que poner en el log. Por defecto, el cliente no accede a la red durante la ejecución de este comando.
svn status
Este ejemplo muestra los posibles estados devueltos:
$ svn status L ./abc.c # fichero bloqueado M ./bar.c # modificado localmente M ./baz.c # modificado localmente en sus propiedades, no en su contenido ? ./foo.o # fichero ignorado ! ./foo.c # fichero borrado por el usuario u otro programa ~ ./qux # versionado como fichero pero es directorio (o viceversa) A + ./moved_dir # añadido con historial de procedencia M + ./moved_dir/README # añadido con historial y tiene modificaciones locales D ./stuff/fish.c # preparado para borrado A ./stuff/loot/bloo.h # preparado para añadir C ./stuff/loot/lump.c # conflictos con un update S ./stuff/other_dir # directorio cambiado a rama G ./foo.c # modificado localmente e incorporados cambios remotos
Al hacer un update/commit, nos indica un número de versión. Para machacar una copia local con una revisión anterior hacemos:
$svn update -r3 U STATUS.txt U src\java.head\overview.html Updated to revision 3. # y de vuelta a la revisión 4 $svn update -r4 U STATUS.txt U src\java.head\overview.html Updated to revision 4.Hemos deshecho los cambios que se hicieron tras la revisión 3. Observa que ahora he colocado una opción a la derecha del comando en vez de a la izquierda. En Subversion no importa donde colocar las opciones.
Es posible mover o renombrar ficheros o directorios sin perder el historial de cambios. El comando move puede usarse para renombrar o para mover de un directorio a otro.
# primero se marca para su renombrado svn move LEEME.txt README.txt A README.tx D LEEME.txt # comprobamos los cambios pendientes svn status D LEEME.txt A + README.txt # y ejecutamos el cambio svn commit -m "Fichero renombrado" Deleting LEEME.txt Adding README.txt Committed revision 5.
También se puede operar directamente en el servidor usando HTTP:
$svn move http://localhost/repos/awpool/trunk/README.txt http://localhost/repos/awpool/trunk/README
Con SVN es posible obtener mensajes de log:
# para todas las revisiones: svn log # idem con información adicional svn log --verbose # para la revisión 4 svn log -r 4 # para un fichero concreto (deberemos estar en # el directorio del fichero o especificar el path completo) svn log README.txt
También podemos mostrar log operando directamente sobre el servidor, sin necesidad de habernos bajado nada:
$svn log http://localhost/repos/awpool
Si con log veíamos el historial de revisiones de un fichero, con blame vemos el fichero entero y el autor de cada cambio sobre cada parte del fichero:
svn blame http://ruta/al/fichero
# entro en el proyecto cd /home/jano/work/trunk # Creo un fichero de nombre arbitrario Changelog con todos los mensajes de log # desde la revisión 1 (-r1) hasta la última revisión (HEAD). svn log -r1:HEAD >> ChangeLog # Hago add porque es un fichero nuevo svn add ChangeLog # subo el fichero para añadirlo a la distribución svn commit ChangeLog -m 'Actualizo ChangeLog'
# Se hace como en CVS, primero se marca para eliminarlo svn rm fichero # y luego se hace commit para eliminarlo en el repositorio. # Si es un directorio, la eliminación se realizará recursivamente por defecto. svn commit # del, delete, remove, y rm, son seudonimos del mismo comando
Si ejecutamos el comando sobre una URL la eliminación será inmediata. Después de ejecutar rm y commit, los ficheros/directorios, habrán desaparecido de la copia local y el HEAD del repositorio, pero permanecerán sus revisiones anteriores.
# añadir un directorio recursivamente (por defecto no se añaden subdirectorios) svn add directorio -R svn commit # añadir un fichero svn add fichero svn commit
# no funciona recursivamente a menos que usemos -R svn revert
Deshace los cambios locales, incluyendo modificaciones, cambios de propiedades, y planificados de cambios (add, rm, para los que aun no hayamos hecho commit).
Los diffs (diferencias) son los datos diferentes entre dos versiones de un fichero (o conjuntos de ficheros), de modo que aplicando esa diferencia es posible pasar de un fichero a otro. Un diff se aplica usando el comando GNU patch. SVN usa el formato unificado diff.
# Obtener los diffs de las modificaciones de la copia local svn diff # diff entre las revisiones 14 y 18 para ficheros *.java y *.xml svn diff -r14:18 *.java *.xml > /home/jano/tmp/r14-a-r18.patch # diff entre un fichero local "foo.c" y su última revisión svn diff --revision HEAD foo.c # lo mismo que el anterior pero sin tener en cuenta nuestras modificaciones locales svn diff --revision BASE:HEAD foo.c
El parametro --revision también acepta fechas, no solo números de revisión.
Una buena práctica de la ingeniería del software es mantener los proyectos en estado compilable y funcional en todo momento. Pero cuando necesitamos introducir un cambio extenso en un proyecto, corremos el riesgo de "dejar en obras" gran parte de la aplicación. Esto suele traducirse en la interrupción de funcionalidades críticas y molestias en el trabajo de otras personas.
Para evitar problemas conviene crear una rama (branch). Una rama es una copia del código principal que evoluciona por separado, pero que tiene un pasado común respecto al origen a partir del cual se creó. En otras palabras, un proyecto propio con el cual experimentar. Más adelante, cuando hayamos completado nuestro trabajo, fundiremos nuestros cambios con la rama principal del proyecto. SVN proporciona un comando merge para integrar una rama en otra.
Suele hablarse de ramas principal y secundaria, pero internamente no existe esa distinción, son simplemente dos directorios del repositorio. De hecho, para Subversion no existe el concepto de rama, es solo un nombre que nosotros empleamos.
Un tema problemático con la fusión de cambios entre ramas, es que mientras trabajamos en una rama, el resto del equipo sigue añadiendo cambios a la principal. Sería deseable que la rama donde trabajamos integrase automáticamente los cambios producidos en el código principal para evitar discrepancias al integrar los cambios en el código principal. Esta funcionalidad esta prevista en alguna de las versiones posteriores a la 1.0, por ahora el soporte de merge es similar al del CVS.
En el siguiente ejemplo tenemos la rama principal en /cygdrive/d/desarrollo/awpool/trunk y crearemos una rama en /cygdrive/d/desarrollo/awpool/branch2-awpool2. Por cierto, estas rutas tan raras son de mi instalación de Cygwin en Windows. Podría haber usado d:\desarrollo\... o cualquiera ruta válida en mi disco duro.
cd /cygdrive/d/desarrollo/awpool svn copy trunk branch-awpool2 svn commit -m "creo la rama branch-awpool2"
La copia no duplica el código en disco, tal como hace CVS, sino que crea enlaces apuntando al código existente. Lo único que ocupará espacio en disco serán los cambios que introduzcamos a partir de aquí.
En los sistemas de control de versiones, un tag (etiqueta) es una etiqueta que marca un punto en la evolución del código. Para Subversion un tag es un nombre que damos a una determinada revisión de un directorio del repositorio.
Para crear una etiqueta también se usa copy:
cd /cygdrive/d/desarrollo/awpool svn copy trunk release-1.0 svn commit -m "marco el proyecto con la etiqueta release-1.0"
Aunque pueda sorprender a los veteranos del CVS, esto es coherente con lo que veíamos en el apartado "Branches". El comando copy crea un enlace a una revisión del código. Si no añadimos cambios es como si hubiéramos etiquetado una versión del código. Si añadimos cambios es como si tuviéramos una rama. Internamente, Subversion no conoce los conceptos etiqueta y rama, solo entiende de directorios del repositorio.
Vamos a crear un fichero trunk.tar.gz que contenga un directorio trunk con todo el proyecto:
cd /home/jano/tmp svn export http://localhost/repos/awpool/trunk tar cf trunk.tar trunk gzip -9 trunk.tar
Vamos a crear una copia de seguridad del repositorio y todas sus versiones en un fichero .gz. Ocupará lo que ocupa el release que hicimos antes, más lo que ocupe la información de las revisiones.
svnadmin dump /home/jano/proyectos/svn | gzip -9 > dump.gz * Dumped revision 0. * Dumped revision 1. * Dumped revision 2. * Dumped revision 3. * Dumped revision 4. * Dumped revision 5.Usando el parametro -r podemos volcar a partir de una revisión concreta.
Si luego queremos restaurar la copia hacemos:
gunzip -c dump.gz | svnadmin load /home/jano/proyectos/svno en Windows:
svnadmin load d:\ruta\al\repositorio < "fichero.descomprimido.dump"
# muestra los comandos disponibles svn help # muestra ayuda sobre un comando concreto svn help comando
Si estamos realizando cambios a la copia de trabajo e interrumpimos con svn cleanup, que terminará las operaciones interrumpidas y eliminará los bloqueos.
o se produce un cuelgue de todo el sistema, es posible que la próxima vez que accedamos nos diga que parte de nuestra copia de trabajo está bloqueada (locked). Esto se selecciona ejecutandoPor ahora no se puede :-(. Esta capacidad se llamará "obliterate" y esta planeada para después de la versión 1.0. Lo máximo que puedes hacer ahora es
eliminar todos los ficheros y dejar vacío el repositorio (el historial permanecerá),
o eliminar físicamente todo el repositorio, crear uno nuevo, y restaurar las copias de seguridad.
Compartir el repositorio entre sistemas operativos es posible, pero no una práctica oficialmente soportada.
Al intentar acceder a un mismo repositorio Windows en FAT32 desde linux aparece este mensaje:
svn: Unable to open an ra_local session to URL svn: Unable to open repository 'file:///mnt/win5/repositorio' svn: Berkeley DB error while opening environment for filesystem /mnt/win5/repositorio: Invalid argument
El problema es que la base de datos de Berkeley usa shared memory (un método de comunicar dos procesos usando una misma región de memoria), y almacena los punteros en ficheros cuyo formato es diferente entre windows y linux.
La solución es eliminar los ficheros repositorio/db/__db.00* y ejecutar svnadmin recover /ruta/al/repositorio.
En el futuro otra opción será usar un repositorio fsfs (svnadmin create --fs-type /ruta/al/repositorio). Se trata de un repositorio que usa el sistema de ficheros en vez de la base de datos de Berkeley. Es una funcionalidad de Subversion que a día de hoy (agosto de 2004) aun no es estable. Consulta la lista de desarrollo de Subversion para más información.
Subversion permite adjuntar pares arbitrarios nombre/valor a ficheros y directorios. Estos metadatos se llaman propiedades en Subversion. Las propiedades son versioneadas, es decir, se almacenan sus sucesivos valores igual que se hace con los ficheros.
Las propiedades son modificaciones locales que no serán permanentes hasta que ejecutemos commit. Como cualquier modificación, las propiedades pueden verse con diff, status, revert.
Para establecer una propiedad se usa svn propset nombre valor fichero. Ejemplo:
svn propset autor jano EstoNoMeEstaPasandoAMiException.java svn propset proposito "ilustrar el uso de propiedades" DeQueVasException.c
También se pueden adjuntar ficheros binarios como propiedades:
svn propset diagrama -F uml.png Command.java
Para leer una propiedad usa svn propget nombre fichero. Ejemplo:
svn propget color Command.java svn propget height Command.java
# ver nombres de propiedades svn proplist Command.java # ver nombres y valores de propiedades svn proplist Command.java --verbose
Las propiedades se editan ejecutando svn propedit nombre fichero. Esto hará que se abran en el editor de texto que hayamos definido. Es útil sobretodo para introducir propiedades con retornos de carro.
Solo para ficheros. Si esta propiedad existe, significa que el fichero es ejecutable. El valor de esta propiedad es irrelevante.
Sirve para indicar el tipo mime. Normalmente no es necesario porque los comandos add e import, incorporan un algoritmo de detección.
Si establecemos el tipo mime a algo que no comience por text/, Subversion supone que es un fichero binario, si no, supone que es de texto simple.
Cuando recuperamos un fichero usando el navegador, el módulo mod_dav_svn lo envía con el valor del mime-type en la cabecera Content-type.
Se aplica a un directorio y permite establecer patrones de ficheros que serán ignorados por SVN. Si queremos crearla, es conveniente usar el comando propget. Esto es similar al .cvsignore del CVS.
svn propedit svn:ignore . *.bak *~
Si nos da un error como este:
svn: Working copy is not up-to-date svn: Commit failed (details follow): svn: Cannot commit propchanges for directory '/home/jano/...
Debemos actualizar el directorio: svn update directorio antes de hacer commit a ese directorio.
Sirve para forzar los códigos de control de los saltos de línea los ficheros. Si desarrollamos desde varios sistemas operativos, podemos emplear esta propiedad para hacer siempre checkout en el salto de línea nativo del sistema que usemos.
Por defecto, los ficheros en el repositorio tienen los mismos saltos de línea que en la copia de trabajo. Los valores posibles son:
LF
CR
CRLF
native: los ficheros aparecen siempre con los saltos de línea propios del sistema operativo local. En el repositorio los ficheros se guardan con LF.
Subversion sustituye cinco palabras clave, aquí se listan junto con su sinónimo:
LastChangedDate, Date Fecha del último cambio, por ejemplo: 2002-07-22 21:42:37 -0700 (Mon, 22 Jul 2002)
LastChangedRevision, Rev Última revisión en que fue cambiado el fichero. Por ejemplo: 18
LastChangedBy, Author Nombre del último usuario que la cambio.
HeadURL, URL de la última versión de este fichero
Id Sumario de las palabras anteriores. Por ejemplo: bar 148 2002-07-28 21:30:43 epg, que significa que el fichero bar cambio en la revisión 148, por el usuario epg, en la fecha 2002-07-28 21:30:43.
Para activar las palabras clave en un fichero se usa la propiedad svn:keywords.
svn propset svn:keywords "palabra1, palabra2, ..." fichero
Ejemplo:
# Esto hace que en el fichero foo.c se inserte el valor de dichas palabras, # allá donde aparezcan las cadenas $Date$, $LastChangedDate$, $Author$, y $LastChangedBy$. svn propset svn:keywords "Date Author" foo.c
Para obtener la propiedad svn:keywords se usa
svn propget svn:keywords fichero
# 1. checkout svn checkout http://svn.example.com/repos/proyecto/trunk # 2. actualizar cambios hechos por otros svn update # 3. ¿que cambios voy a enviar? svn status # 4. enviar mis cambios svn commit –m "cambie tal y tal cosa" # 5. poner ficheros y directorios bajo el control de subversion svn add fichero1 fichero2 subdirectorio svn commit –m "añado dos ficheros y un directorio" # 6. borrar ficheros y directorios svn delete fichero1 fichero2 subdirectorio svn commit –m "borre lo de antes porque no me molaba"