Ahora usted y Carmen están trabajando en ramas paralelas del proyecto: usted está trabajando en una rama privada, y Carmen está trabajando en el tronco, o línea principal de desarrollo.
En proyectos que tienen grandes cantidades de contribuyentes, es habitual que la mayoría tengan copias locales del tronco. Cuando alguien necesita hacer un cambio a largo plazo que puede molestar en el tronco, un procedimiento estándar es crear una rama privada y realizar ahí los cambios hasta que todo el trabajo esté completo.
Así que las buenas noticias son que usted y Carmen no están interfiriendo en sus trabajos. Las malas noticias son que es muy fácil derivar demasiado lejos. Recuerde que uno de los problemas con la estrategia “esconderse en un agujero” es que para cuando haya terminado con su rama, puede que sea casi imposible fusionar los cambios con el tronco sin un alto número de conflictos.
En su lugar, usted y Carmen pueden continuar compartiendo cambios a medida que trabajan. Es tarea suya decidir qué cambios merece la pena compartir; Subversion le brinda la posibilidad de “copiar” cambios entre ramas de forma selectiva. Y para cuando haya finalizado completamente con su rama, su conjunto entero de cambios en la rama puede ser copiado en el tronco.
El la sección anterior, mencionamos que tanto usted
como Carmen han realizado cambios a
integer.c
en ramas diferentes.
Si observa el informe de cambios de la revisión 344,
puede ver que ella realizó algunas correcciones de
ortografía. Sin duda, su copia del mismo fichero debe tener
todavía los mismos errores de ortografía. Es probable que
futuros cambios al fichero afectarán las mismas áreas que
tienen estos errores ortográficos, lo que potencialmente
generará conflictos cuando fusione su rama algún día. Es
mejor, entonces, recibir ahora los cambios de Carmen,
antes de que comience a trabajar de
forma intensiva en el mismo sitio.
Es el momento de usar el comando svn merge. Este comando, descubrirá que es un primo cercano del comando svn diff (sobre el cual leímos en el tercer capítulo). Ambos comandos son capaces de comparar dos objetos cualquiera del repositorio y describir sus diferencias. Por ejemplo, puede preguntar a svn diff que le muestre el cambio exacto realizado por Carmen en la revisión 344:
$ svn diff -r 343:344 http://svn.example.com/repos/calc/trunk Index: integer.c =================================================================== --- integer.c (revision 343) +++ integer.c (revision 344) @@ -147,7 +147,7 @@ case 6: sprintf(info->operating_system, "HPFS (OS/2 or NT)"); break; case 7: sprintf(info->operating_system, "Macintosh"); break; case 8: sprintf(info->operating_system, "Z-System"); break; - case 9: sprintf(info->operating_system, "CPM"); break; + case 9: sprintf(info->operating_system, "CP/M"); break; case 10: sprintf(info->operating_system, "TOPS-20"); break; case 11: sprintf(info->operating_system, "NTFS (Windows NT)"); break; case 12: sprintf(info->operating_system, "QDOS"); break; @@ -164,7 +164,7 @@ low = (unsigned short) read_byte(gzfile); /* read LSB */ high = (unsigned short) read_byte(gzfile); /* read MSB */ high = high << 8; /* interpret MSB correctly */ - total = low + high; /* add them togethe for correct total */ + total = low + high; /* add them together for correct total */ info->extra_header = (unsigned char *) my_malloc(total); fread(info->extra_header, total, 1, gzfile); @@ -241,7 +241,7 @@ Store the offset with ftell() ! */ if ((info->data_offset = ftell(gzfile))== -1) { - printf("error: ftell() retturned -1.\n"); + printf("error: ftell() returned -1.\n"); exit(1); } @@ -249,7 +249,7 @@ printf("I believe start of compressed data is %u\n", info->data_offset); #endif - /* Set postion eight bytes from the end of the file. */ + /* Set position eight bytes from the end of the file. */ if (fseek(gzfile, -8, SEEK_END)) { printf("error: fseek() returned non-zero\n");
El comando svn merge es casi exactamente idéntico. En lugar de mostrar las diferencias en su terminal, no obstante, las aplica directamente a su copia local como modificaciones locales:
$ svn merge -r 343:344 http://svn.example.com/repos/calc/trunk U integer.c $ svn status M integer.c
La salida del comando svn merge
muestra que su copia de integer.c
fue parcheada. Ahora contiene el cambio de Carmen—el
cambio ha sido “copiado” desde el tronco a su
copia local de la rama privada, y existe ahora como una
modificación local. En este punto, es tarea suya revisar
la modificación local para asegurarse de que funciona
correctamente.
En otra situación, es posible que las cosas no hayan
ido tan bien, y que integer.c
haya entrado en un estado de conflicto. Puede tener que
resolver el conflicto usando el procedimiento estándar
(vea el tercer capítulo), o si decide que la fusión fue una
mala idea, simplemente dé se por vencido y use svn
revert para revertir el cambio local.
Pero asumiendo que ha revisado el cambio fusionado, puede guardar sus cambios como es habitual con svn commit. En ese punto, el cambio ha sido fusionado en su rama del repositorio. En terminología de control de versiones, este acto de copiar cambios entre ramas se denomina portar cambios.
Cuando guarda su modificación local, asegúrese de que el mensaje de cambios indica que está portando un cambio específico de una rama a otra. Por ejemplo:
$ svn commit -m "integer.c: ported r344 (spelling fixes) from trunk." Sending integer.c Transmitting file data . Committed revision 360.
Tal y como verá en las siguientes secciones, es muy importante seguir esta “práctica recomendada”.
Aviso: aunque los comandos svn diff y svn merge son similares en concepto, tienen una diferente sintaxis en muchos casos. Asegúrese de leer en el capítulo 8 los detalles, o pregunte a svn help. Por ejemplo, svn merge requiere una ruta a una copia local como destino, es decir, un lugar donde debe aplicar los cambios del árbol. Si el destino no es especificado, asume que está intentando realizar una de las siguientes operaciones comunes:
Quiere fusionar cambios de directorio directamente en su copia local.
Quiere fusionar los cambios de un fichero específico en un fichero que existe con el mismo nombre en su directorio de trabajo actual.
Si está fusionando un directorio y no ha especificado una ruta destino, svn merge asume el primer caso mencionado e intenta aplicar los cambios en su directorio actual. Si está fusionando un fichero, y ese fichero (o un fichero con el mismo nombre) existe en su directorio actual de trabajo, svn merge asume el segundo caso mencionado e intenta aplicar los cambios al fichero local de mismo nombre.
Si quiere aplicar los cambios en otro lugar, tendrá que especificarlo. Por ejemplo, si todavía está en el directorio padre de su copia local, tendrá que especificar el directorio destino para recibir los cambios:
$ svn merge -r 343:344 http://svn.example.com/repos/calc/trunk my-calc-branch U my-calc-branch/integer.c
Fusionar cambios suena bastante simple, pero en la práctica puede convertirse en un dolor de cabeza. El problema es que si fusiona cambios con frecuencia de una rama a otra, puede acabar fusionando accidentalmente el mismo cambio dos veces. Cuando esto ocurre, a veces las cosas seguirán funcionando bien. Cuando aplica un parche a un fichero, Subversion normalmente se da cuenta de que el fichero ya contiene el cambio, y no hace nada. Pero si el cambio ya existente fue modificado de algún modo, obtendrá un conflicto.
Idealmente, su sistema de control de versiones debería prevenir la doble aplicación de cambios a una rama. Debería recordar automáticamente qué cambios fueron recibidos en la rama, y ser capaz de mostrarle un listado de los mismos. Debería poder usar esta información para automatizar las fusiones tanto como sea posible.
Desafortunadamente, Subversion no es tal sistema. Al igual que CVS, Subversion 1.0 no almacena ninguna información sobre operaciones de fusionado. Cuando envía sus cambios locales, el repositorio no tiene idea si esos cambios vinieron de una ejecución del comando svn merge, o de una modificación manual de los ficheros.
¿Qué significa esto para usted, el usuario? Significa que hasta que llegue el día que Subversion desarrolle esta característica, tendrá que gestionar usted mismo la información de fusionado. El mejor lugar para hacerlo es en el informe de cambios cuando envía cambios al repositorio. Tal y como se demostró en el ejemplo anterior, es recomendable que su informe de cambios mencione el número de revisión específico (o rango de revisiones) que está siendo fusionado en su rama. Más tarde, puede ejecutar svn log para revisar qué cambios contiene ya su rama. Esto le permitirá construir con cuidado siguientes comandos svn merge que no serán redundantes con cambios previamente portados.
En la siguiente sección, le mostraremos algunos ejemplos de esta técnica en acción.
Dado que el fusionado sólo genera modificaciones locales, normalmente no es una operación de alto riesgo. Si se equivoca en el fusionado la primera vez, simplemente ejecute svn revert para ignorar los cambios y pruebe de nuevo.
Es posible, no obstante, que su copia local ya tenga algunas modificaciones. Los cambios aplicados por una fusión se mezclarán con sus modificaciones locales, por lo que ejecutar svn revert ya no es una opción. Los dos conjuntos de cambios quizás sean imposibles de separar.
En casos como este, la gente se reconforta con la idea
de ser capaces de predecir o examinar las fusiones antes
de que ocurran. Una manera simple de hacerlo es ejecutar
svn diff con los mismos argumentos que
planea pasar a svn merge, tal y como
ya le enseñamos en el primer ejemplo de fusionado. Otro
método de visualización previa es pasar la opción
--dry-run
al comando de fusionado:
$ svn merge --dry-run -r 343:344 http://svn.example.com/repos/calc/trunk U integer.c $ svn status # nothing printed, working copy is still unchanged.
La opción --dry-run
en realidad no
aplica ningún cambio a su copia local. Únicamente muestra
los códigos de estado que serían
mostrados durante una fusión de verdad. Esto es útil
para obtener una visualización previa de “alto
nivel” de la potencial fusión, para aquellos
casos en los que ejecutar svn diff
proporcionaría demasiados detalles.
Cuando hable con un desarrollador de Subversion, es muy posible que oiga alguna referencia al término ascendencia. Esta palabra es usada para describir la relación entre dos objetos en un repositorio: si ambos están relacionados, entonces se dice que un objeto es el ancestro del otro.
Por ejemplo, suponga que envía cambios al repositorio
en la revisión 100, los cuales incluyen un
cambio al fichero foo.c
.
Entonces foo.c@99
es un
“ancestro” de foo.c@100
.
Por otro lado, suponga que hace efectivo el borrado
de foo.c
en la revisión 101, y
entonces añade un nuevo fichero con el mismo nombre en la
revisión 102. En este caso, foo.c@99
y
foo.c@102
pueden parecer relacionados
(tienen la misma ruta), pero de hecho son objetos
completamente diferentes en el repositorio. No comparten
historial o “ascendencia”.
La razón de mencionar esto ahora es para señalar
una diferencia importante entre svn diff
y svn merge. El primer comando
ignora la ascendencia, mientras que el segundo es muy
sensitivo a ella. Por ejemplo, si usase svn
diff para comparar las revisiones 99 y 102 de
foo.c
, vería ficheros diferenciales
basados en líneas; el comando diff esta comparando
a ciegas ambas rutas. Pero si usase svn
merge para comparar ambos objetos, se daría
cuenta de que no están relacionados y primero intentaría
borrar el fichero antiguo, y entonces añadir el nuevo;
vería D foo.c
seguido por A
foo.c
.
En la mayoría de las fusiones se comparan árboles que están relacionados por ascendencia, y por esta razón svn merge sigue este comportamiento por defecto. No obstante, ocasionalmente puede querer que el comando de fusionado compare dos árboles no relacionados. Por ejemplo, puede haber importado dos árboles de código fuente representando diferentes versiones de un proyecto de software (lea “Ramas de proveedores”). ¡Si usase svn merge para comparar los dos árboles, vería que el primero es borrado completamente, seguido de una adición completa del segundo!
En estas situaciones, querrá que svn
merge realice una comparación basada
únicamente en rutas de fichero, ignorando cualquier
relación existente entre ficheros y directorios. Añada
la opción --ignore-ancestry
a su
comando de fusionado, y se comportará del mismo
modo que svn diff. (Y de modo
similar,
la opción --notice-ancestry
hará que
svn diff se comporte como un comando
de fusionado.)
[15] En el futuro, el proyecto Subversion planea usar (o inventar) un formato de parche que describe cambios a árboles.