Hay muchos usos diferentes para svn merge, y esta sección describe los más comunes que posiblemente usará alguna vez.
Para completar el ejemplo que hemos ido mostrando, nos moveremos hacia adelante en el tiempo. Suponga que han pasado varios días, y se han realizado muchos cambios tanto en el tronco como en su rama privada. Suponga que ya ha terminado de trabajar en su rama privada; la característica o corrección está finalmente completada, y ahora quiere fusionar todos los cambios de su rama en el tronco para que otros puedan disfrutarlos.
¿Cómo usamos svn merge en esta
situación? Recuerde que este comando compara dos árboles,
y aplica las diferencias en una copia local. Así que para
recibir los cambios, necesita tener una copia local del
tronco. Asumiremos que todavía tiene una copia original
del tronco por alguna parte (completamente actualizada),
o que recientemente ha obtenido una copia local fresca de
/calc/trunk
.
¿Pero qué dos árboles deberían ser comparados? A primera vista, la respuesta puede parecer obvia: simplemente compare el último árbol del tronco con el último árbol de su rama. ¡Pero cuidado—esta asunción es incorrecta, y ha confundido a muchos usuarios nuevos! Dado que svn merge funciona como svn diff, comparar el tronco y su rama no describirá meramente el conjunto de cambios realizados en su rama. Tal comparación muestra demasiados cambios: no solo mostraría la adición de los cambios en su rama, sino también el borrado de cambios en el tronco que nunca ocurrieron en su rama.
Para expresar únicamente los cambios que ocurrieron en
su rama, necesita comparar el estado inicial de su rama
con su estado final. Usando svn log
en su rama, puede ver que ésta fue creada en la revisión
341. Y el estado final puede obtenerlo con la revisión
HEAD
. Esto significa que quiere comparar
las revisiones 341 y HEAD
del directorio
de su rama, y aplicar esas diferencias a una copia local
del tronco.
Sugerencia | |
---|---|
Un buen método para encontrar la revisión en
la cual fue creada una rama (la "base" de la misma)
es usar la opción Así que en nuestro ejemplo continuo: $ svn log --verbose --stop-on-copy \ http://svn.example.com/repos/calc/branches/my-calc-branch … ------------------------------------------------------------------------ r341 | usuario | 2002-11-03 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: A /calc/branches/my-calc-branch (from /calc/trunk:340) $ Como esperábamos, el número final de revisión
mostrado por este comando es la revisión en la cual
|
De nuevo, aquí está el procedimiento final de fusionado:
$ cd calc/trunk $ svn update At revision 405. $ svn merge -r 341:HEAD http://svn.example.com/repos/calc/branches/my-calc-branch U integer.c U button.c U Makefile $ svn status M integer.c M button.c M Makefile # ...examine the diffs, compile, test, etc... $ svn commit -m "Merged my-calc-branch changes r341:405 into the trunk." Sending integer.c Sending button.c Sending Makefile Transmitting file data ... Committed revision 406.
De nuevo, fíjese que el mensaje de cambios menciona de forma explícita el rango de cambios que fue fusionado en el tronco. Recuerde siempre hacer esto, porque es información crítica que necesitará más adelante.
Por ejemplo, suponga que decide continuar trabajando en
su rama otra semana, para completar una mejora a una de
las características originales o corregir un fallo. La
revisión HEAD
del repositorio es ahora
480, y está listo para realizar otra fusión desde su
rama privada al tronco. Pero como se discutió en “Procedimientos ideales de fusionado”, no quiere fusionar los
cambios que ya fueron fusionados; sólo quiere fusionar todo
lo “nuevo” en su rama desde la última vez que
fue fusionada. El truco es descubrir qué es lo nuevo.
El primer paso es ejecutar svn log en el tronco, y buscar un mensaje de cambios sobre la última vez que realizó una fusión desde su rama:
$ cd calc/trunk $ svn log … ------------------------------------------------------------------------ r406 | usuario | 2004-02-08 11:17:26 -0600 (Sun, 08 Feb 2004) | 1 line Merged my-calc-branch changes r341:405 into the trunk. ------------------------------------------------------------------------ …
¡Ajá! Dado que todos los cambios que ocurrieron en la
rama entre las revisiones 341 y 405 fueron fusionados
con el tronco como la revisión 406, ahora sabe que sólo
quiere fusionar los cambios de la rama posteriores
a esto—comparando las revisiones 406 y
HEAD
.
$ cd calc/trunk $ svn update At revision 480. # We notice that HEAD is currently 480, so we use it to do the merge: $ svn merge -r 406:480 http://svn.example.com/repos/calc/branches/my-calc-branch U integer.c U button.c U Makefile $ svn commit -m "Merged my-calc-branch changes r406:480 into the trunk." Sending integer.c Sending button.c Sending Makefile Transmitting file data ... Committed revision 481.
Ahora el tronco contiene la segunda oleada completa de cambios realizados en la rama. En este punto, puede o bien borrar su rama (discutiremos esto más adelante), o continuar trabajando en su rama y repetir este procedimiento en siguientes operaciones de fusionado.
Otro uso común de svn merge
es deshacer un cambio que acaba de ser enviado al
repositorio. Suponga que está trabajando felizmente en
su copia local de /calc/trunk
,
y descubre que el cambio realizado hace tiempo en la
revisión 303, que modificó integer.c
,
está completamente mal. Nunca debería haber llegado al
repositorio. Puede usar svn merge
para “deshacer” el cambio en su copia
local, y entonces enviar las modificaciones locales al
repositorio. Todo lo que necesita hacer es especificar la
diferencia al revés:
$ svn merge -r 303:302 http://svn.example.com/repos/calc/trunk U integer.c $ svn status M integer.c $ svn diff … # verify that the change is removed … $ svn commit -m "Undoing change committed in r303." Sending integer.c Transmitting file data . Committed revision 350.
Una forma de pensar sobre las revisiones del repositorio
es como un grupo de cambios (algunos sistemas de control de
versiones los llaman changesets). Al
usar el parámetro -r
, puede pedirle a
svn merge que aplique el changeset,
o el rango completo de changesets sobre su copia local de
trabajo. En nuestro caso para deshacer un cambio usamos
svn merge para aplicar el changeset #303
a nuestra copia local al revés.
Tenga presente que deshacer un cambio de esta manera es
como realizar cualquier otra operación svn
merge, así que debería usar svn
status y svn diff para
confirmar que su trabajo está en el estado que usted
desea, y entonces usar svn commit para
enviar la versión final al repositorio. Tras el envío,
este changeset particular ya no se verá reflejado en la
revisión HEAD
.
De nuevo puede estar preguntándose: vaya, eso no deshizo
el cambio anterior, ¿verdad? El cambio todavía existe en la
revisión 303. Si alguien obtiene una versión del proyecto
calc
entre las revisiones 303 y 349,
seguirán viendo todavía el cambio malo, ¿verdad?
Si, en efecto. Cuando hablamos sobre
“eliminar” un cambio, estamos hablando sobre
eliminarlo de HEAD
. El cambio original
todavía existe en la historia del repositorio. En la
mayoría de las situaciones, esto es suficiente. De todos
modos la mayoría de las personas sólo están interesadas en
seguir la versión HEAD
de un proyecto.
No obstante, hay casos especiales en los que realmente
desearía destruir toda evidencia de un cambio. (Quizás
alguien puso accidentalmente en el repositorio un documento
confidencial.) Esto, tal y como verá, no es tan fácil porque
Subversion fue diseñado deliberadamente para nunca perder
información. Las revisiones son árboles inmutables que se
construyen unos sobre otros. Eliminar una revisión de la
historia podría causar un efecto dominó, creando caos en
todas las revisiones siguientes y posiblemente invalidando
todas las copias locales de trabajo.
[16]
La mejor característica de los sistemas de control
de versiones es que la información nunca se pierde. Incluso
cuando borra un fichero o un directorio, puede haberse
esfumado de la revisión HEAD
, pero el
objeto todavía existe en revisiones anteriores. Una de
las preguntas más comunes realizadas por nuevos usuarios
es, “¿Cómo puedo recuperar mi fichero o directorio
antiguo?”
El primer paso es definir exactamentequé elemento está intentando resucitar. Aquí tiene una metáfora útil: puede pensar que todo objeto en el repositorio existe en una especie de sistema de coordenadas bidimensional. Las abscisas marcan una revisión particular del árbol, y las ordenadas una ruta de fichero dentro de ese árbol. Así que cualquier versión de su fichero o directorio puede definirse por un par específico de coordenadas.
Subversion no tiene un directorio
Attic
como CVS,
[17]
por lo que necesita usar el comando svn
log para descubrir la pareja de coordenadas
exactas que quiere resucitar. Una buena estrategia
es ejecutar svn log --verbose en un
directorio que solía contener el elemento borrado. La opción
--verbose
muestra una lista de todos
los elementos modificados en cada revisión; todo lo que
necesita hacer es encontrar la revisión en la cual borró
el fichero o directorio. Puede hacer esto visualmente, o
usando otra herramienta para examinar el informe de cambios
(vía grep, o quizás con una búsqueda
incremental en un editor.)
$ cd parent-dir $ svn log --verbose … ------------------------------------------------------------------------ r808 | jose | 2003-12-26 14:29:40 -0600 (Fri, 26 Dec 2003) | 3 lines Changed paths: D /calc/trunk/real.c M /calc/trunk/integer.c Added fast fourier transform functions to integer.c. Removed real.c because code now in double.c. …
En el ejemplo, estamos asumiendo que está buscando el
fichero borrado real.c
. Leyendo los
informes de cambios del directorio padre, hemos encontrado
que este fichero fue borrado en la revisión 808. Por lo
tanto la última versión del fichero existió en la revisión
inmediatamente anterior a esa. Conclusión: quiere resucitar
la ruta /calc/trunk/real.c
de la
revisión 807.
Eso fue la parte difícil—la investigación. Ahora que sabe lo que quiere recuperar, tiene dos posibles opciones.
Una de las opciones es usar el comando svn
merge para aplicar la revisión 808 “al
revés”. (Ya hemos discutido cómo deshacer cambios,
lea “Deshaciendo cambios”.) Esto tendría
el efecto de reañadir real.c
como
modificación local. El fichero se programaría para ser
añadido, y tras enviar los cambios, el fichero existiría
de nuevo en HEAD
.
No obstante, en este ejemplo particular, ésta
probablemente no es la mejor estrategia. Aplicar al revés
la revisión 808 no solamente programaría la adición
de real.c
, ya que el mensaje de
cambios indica que también se desharían ciertos cambios
a integer.c
, cosa que no desea.
Ciertamente, podría fusionar al revés la revisión 808
y entonces ejecutar svn revert sobre
la modificación local de integer.c
,
pero esta técnica no es muy escalable. ¿Qué pasa si hay 90
ficheros modificados en la revisión 808?
Otra estrategia más precisa es usar svn copy en lugar de svn merge. Simplemente copie la “pareja de coordenadas” exactas de revisión y ruta del repositorio a su copia de trabajo local:
$ svn copy --revision 807 \ http://svn.example.com/repos/calc/trunk/real.c ./real.c $ svn status A + real.c $ svn commit -m "Resurrected real.c from revision 807, /calc/trunk/real.c." Adding real.c Transmitting file data . Committed revision 1390.
El símbolo de suma en la salida del comando indica que
el elemento no sólo se ha programado para ser añadido, sino
para ser añadido “con historia.” Subversion
recuerda de dónde fue copiado. En el futuro, ejecutar
svn log sobre este fichero recorrerá
su historia hasta la resurrección y continuará también con
la que tenía antes de la revisión 807. En otras palabras,
este nuevo real.c
no es realmente
nuevo; es un descendiente directo del fichero original que
fue borrado.
A pesar de que nuestro ejemplo muestra cómo resucitar un fichero, tenga en cuenta que esta misma técnica proporciona los mismos resultados al resucitar directorios borrados.
[16] Sin embargo, el proyecto Subversion tiene planes para implementar algún día un comando svnadmin obliterate que realizaría la tarea de borrar información permanentemente. Mientras tanto, lea “svndumpfilter” para una posible solución alternativa.
[17] Dado que CVS no hace versionado de árboles,
crea un área Attic
dentro de cada
directorio del repositorio para poder recordar los ficheros
borrados.