lunes, 28 de diciembre de 2009

Tutorial: ¿Cómo migrar desde Subversion (SVN) hacia Mercurial con Hgsvn?

Mercurial SCM

Después de sumarme al desarrollo del plugin de Trac para RPC (gracias a Odd Simon Simonsen ;o) y trabajar en unas mejoras, he decidido migrar mis proyectos anteriores desde Subversion hacia Mercurial. Aquí describiré una alternativa para realizarla. Por el momento no soy un experto, por lo que el análisis de las ventajas y puntos débiles de esta variante quedará pospuesto hasta que pruebe otras alternativas (si es que eso ocurre ...). Sin embargo, Usted puede formar parte de la búsqueda de la solución más adecuada y opinar u ofrecer sugerencias. Si desea saber acerca de cómo compartir código y colaborar utilizando repositorios de Mercurial, le sugiero que consulte un artículo previo sobre el uso de MQ con Bitbucket. Es así cómo se desarrolla el plugin que les mencioné anteriormente, utilizando su repositorio de parches.

Actualizado 23/04/2010 13:43:09 con hgpullsvn

Estado inicial : Subversion

Subversion ha sido por mucho tiempo la opción más difundida para desarrollar proyectos de software libre. De hecho este es el sistema de control de versiones que fue ofrecido en primer lugar por Sourceforge, Google Code y casi todos los otros grandes patrocinadores del open source. Sin embargo el surgimiento primero de GitHub, luego de Bitbucket y otros sitios similares basados en Bazaar, han hecho que los sistemas distribuidos de control de versiones ganen una fuerza tremenda. En gran parte esto se debe a la inmensa gama de modelos de colaboración que son posibles cuando no se tiene un repositorio centralizado. De hecho, desde que comencé a utilizar SVN la sugerencia que nos hacia mi profesor, Alí, era que le echáramos un vistazo a SVK.

En este momento se gestó el proyecto FLiOOPS con una idea original de Kyrie (a.k.a. Luis Alberto Zarrabeitia) para concebir una implementación de la programación por contratos en Python. Lo que una vez fue una tarea de curso, con el tiempo fue creciendo y se incorporaron nuevas ideas. Entre todas ellas se destacó el módulo dutest, que se utilizó para escribir las pruebas de unidad. El aumento en la complejidad, implicó la existencia de varias ramas de desarrollo y otros elementos a los que Subversion no les da una solución. Por ejemplo acá les muestro la estructura actual del repositorio original :

/
    java
        trunk
        branches
        tags
    py
        trunk
        branches
            add_opts
            mdl_inv
            pep302
            test_pycontract
            guarded_synch
            ochk_arch
            pysimrec
        tags
            dutest
                0_1_0
                0_1_1
                0_1_2
                0_2_1
                0_2_2

La buena noticia es que Sourceforge permite también el uso de (múltiples) repositorios de Mercurial y Git. Inicialmente esto hace posible tener un repositorio para cada lenguaje de programación, o para cada librería que se desarrolle en el marco del proyecto. Sin embargo hay que trasladar todas las versiones ya existentes hacia su nuevo destino.

Opciones para migrar

Después de buscar información e indagar un poco, encontré varias herramientas, pero parece que tres se distinguen del resto:

  • HgSubversion es una librería externa cuyo objetivo es lograr una integración completa con Subversion.
  • El comando convert permite migrar repositorios de distintos tipos (incluso otros repositorios hechos con Mercurial) hacia un repositorio de Mercurial. Como permite filtrar y renombrar los contenidos que serán incorporados, también resulta útil para filtrar repositorios Mercurial completos y obtener un subconjunto de los cambios que resultan de interés. Entre los tipos de herramientas soportadas se encuentran :
  • Hgsvn es un projrcto que surgió a partir de la comunidad de desarollo de Python para dar una respuesta a la migración de los repositorios de la librería estándar. Varias de sus características son:
    • Los datos de las versiones se solicitan por lotes.
    • El proceso es incremental, por lo que es posible interrumpirlo y resumirlo posteriormente.
    • Preserva los meta-datos (e.g. nombres y fechas de los cambios, registros de copia y de cambio de nombres de ficheros).
    • Crea marcas (a.k.a. tags) locales para cada versión en el repositorio original.
    • Preserva los nombres y las relaciones entre las ramas (a.k.a. branches).

En todos los casos parece que el punto débil resulta ser la posibilidad de hacer commits en el repositorio SVN. Sin embargo, como ya les expliqué, este no era mi objetivo en este caso. Finalmente me decidí a utilizar Hgsvn.

Clonando el repositorio con Hgsvn

Hgsvn solo clona una rama a la vez. Considerando la estructura del repositorio original, el primer paso es clonar el trunk. Para ello se ejecuta el comando hgimportsvn de la siguiente manera:

$ pwd
/home/olemis/repos/
$ mkdir flioops
$ cd flioops
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/trunk > ./trunk.log
$ cd trunk && hgpullsvn >> ../trunk.log

En el ejemplo se redirecciona la salida hacia el fichero ./trunk.log para su posterior análisis. Les recomiendo observarlo, puesto a que conocerán los cambios que se van incorporando y los datos que se van recuperando del repositorio original. La lista puede ser larga.

En este caso se crea un repositorio dentro de la carpeta trunk. En general el nombre se corresponde con el último tramo de la URL que se especifica como parámetro. Podemos comprobar que todo está en orden después de observar el log del nuevo repositorio.

$ pwd
/home/olemis/repos/flioops/trunk
$ hg log | more
changeset:   49:39cfb58b31b6
branch:      trunk
tag:         tip
tag:         svn.91
user:        olemis
date:        Wed Sep 17 10:34:32 2008 -0500
summary:     [svn r91] - Bug fixed... The class PackageTestLoader loaded the tests defined in

changeset:   48:5595c9f190ca
branch:      trunk
tag:         svn.88
user:        olemis
date:        Thu Sep 11 14:38:56 2008 -0500
summary:     [svn r88] Starting dutest release 0.2.1.

changeset:   47:ac470dd94852
branch:      trunk
tag:         svn.86
user:        olemis
date:        Thu Sep 11 12:10:25 2008 -0500
summary:     [svn r86] - PackageTestLoader class added to oop.utils.dutest module. It loads

$ hg branches
trunk                         49:39cfb58b31b6

$ hg tags -v | more
tip                               49:39cfb58b31b6
svn.91                            49:39cfb58b31b6 local
svn.88                            48:5595c9f190ca local
svn.86                            47:ac470dd94852 local
svn.83                            46:0f8df4313c98 local
svn.82                            45:287eef1166bf local
svn.80                            44:7accc993aad9 local
svn.78                            43:9ab9af3bbbfd local
svn.77                            42:4678ec09ef71 local

$ hg log -r svn.88 -v
changeset:   48:5595c9f190ca
branch:      trunk
tag:         svn.88
user:        olemis
date:        Thu Sep 11 14:38:56 2008 -0500
files:       utils/dutest.py
description:
[svn r88] Starting dutest release 0.2.1.

Como se puede observar solo obtenemos una sola rama llamada trunk. Su nombre se selecciona de manera análoga a partir de la URL. También se crea una marca local para cada revisión que se encuentra en el repositorio original. De esta forma es más fácil relacionar los cambios en un repositorio y otro. Por ejemplo la versión 88 en el repositorio central se corresponde con la versión 48 localmente, cuyo identificador global sería 5595c9f190ca. Además, si se nos olvidan estos detalles (de más está decir que yo me los sé de memoria :P ) pero conocemos la versión en el repositorio de Subversion, entonces es posible hacer referencia a su contra-parte en el repositorio de Mercurial utilizando el nombre (i.e. marca) svn.88.

Como se puede observar se mantiene el nombre del autor de los cambios y la fecha. Quizás le puedan quedar dudas ... pues ¡aclarémoslas!.

$ svn info
Path: .
URL: https://flioops.svn.sourceforge.net/svnroot/flioops/py/trunk
Repository Root: https://flioops.svn.sourceforge.net/svnroot/flioops
Repository UUID: 878b2ac5-cb47-0410-a52a-c42bf53788ce
Revision: 91
Node Kind: directory
Schedule: normal
Last Changed Author: olemis
Last Changed Rev: 91
Last Changed Date: 2008-09-17 10:34:32 -0500 (Wed, 17 Sep 2008)

Pues bien, en un mismo espacio coexisten ambos repositorios. Compare esta fecha con la que reporta Mercurial y verá que son idénticas. Después de satisfacer su curiosidad inicial, es hora entonces de clonar el resto de los cambios.

Clonando las ramas con Hgsvn

Como hemos visto antes, Hgsvn solo clona una rama a la vez. El proceso para recuperar los cambios restantes es análogo.

$ cd ..
$ pwd
/home/olemis/repos/flioops
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/add_opts > ./add_opts.log
$ cd add_opts && hgpullsvn >> ../add_opts.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/mdl_inv > ./mdl_inv.log
$ cd mdl_inv && hgpullsvn >> ../mdl_inv.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/pep302 > ./pep302.log
$ cd pep302 && hgpullsvn >> ../pep302.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/test_pycontract > ./test_pycontract.log
$ cd test_pycontract && hgpullsvn >> ../test_pycontract.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/guarded_synch > ./guarded_synch.log
$ cd guarded_synch && hgpullsvn >> ../guarded_synch.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/ochk_arch > ./ochk_arch.log
$ cd ochk_arch && hgpullsvn >> ../ochk_arch.log && cd ..
$ hgimportsvn https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/pysimrec > ./pysimrec.log
$ cd pysimrec && hgpullsvn >> ../pysimrec.log && cd ..
$ ls
add_opts          mdl_inv          pep302           test_pycontract
guarded_synch     ochk_arch        pysimrec         trunk
add_opts.log      mdl_inv.log      pep302.log       test_pycontract.log
guarded_synch.log ochk_arch.log    pysimrec.log     trunk.log

Ahora todos esperamos efectos similares (i.e. marcas locales, una sola rama, fechas consistentes y dos repositorios en fraternal coexistencia).

$ cd add_opts
$ hg log | more
changeset:   36:660d2a6c5f05
branch:      add_opts
tag:         tip
tag:         svn.54
user:        olemis
date:        Thu Aug 07 09:42:48 2008 -0500
summary:     [svn r54] The error messages can be formatted now by specifying the templates

changeset:   35:0243dc200611
branch:      add_opts
tag:         svn.53
user:        olemis
date:        Thu Aug 07 09:36:52 2008 -0500
summary:     [svn r53] The whole assertion checking infrastructure is prepared for

changeset:   34:84a28bf08590
branch:      add_opts
tag:         svn.52
user:        olemis
date:        Thu Aug 07 09:22:20 2008 -0500
summary:     [svn r52] The function oop.utils.NormalizeException is prepared for assuming

$ svn info
Path: .
URL: https://flioops.svn.sourceforge.net/svnroot/flioops/py/branches/add_opts
Repository Root: https://flioops.svn.sourceforge.net/svnroot/flioops
Repository UUID: 878b2ac5-cb47-0410-a52a-c42bf53788ce
Revision: 54
Node Kind: directory
Schedule: normal
Last Changed Author: olemis
Last Changed Rev: 54
Last Changed Date: 2008-08-07 09:42:48 -0500 (Thu, 07 Aug 2008)

$ hg branches
add_opts                      36:660d2a6c5f05
trunk                         29:536184bf7e16 (inactive)

$ hg heads | grep changeset
changeset:   36:660d2a6c5f05

$ hg tags -v
tip                               36:660d2a6c5f05
svn.54                            36:660d2a6c5f05 local
svn.53                            35:0243dc200611 local
svn.52                            34:84a28bf08590 local
svn.50                            33:7e5b743efd37 local
svn.49                            32:901f61877293 local
svn.48                            31:51e7d78f26d8 local
svn.47                            30:336637e2a0f1 local

$ hg log -b trunk | grep changeset
changeset:   29:536184bf7e16
changeset:   28:ec176d410993
changeset:   27:d6ddd117926d
changeset:   26:1f89e8573172
changeset:   25:7b0847a67a80
changeset:   24:23571cc5686b
changeset:   23:cad71e6a4e34
changeset:   22:2d9e43109e87
changeset:   21:2a77c9c5e6ad
changeset:   20:c177d00b5a4e
changeset:   19:64eb1c7cdd91
changeset:   18:7c941992dbe5
changeset:   17:4dd286854034
changeset:   16:548c66a002c2
changeset:   15:7ad0521ce2b4
changeset:   14:f0087f96d466
changeset:   13:2540d610c198
changeset:   12:ee7e670e9b88
changeset:   11:8ba039171b0a
changeset:   10:af39f33a1776
changeset:   9:c95d926d676d
changeset:   8:28535deb9fc9
changeset:   7:1ddfa139931c
changeset:   6:84e23e55166e
changeset:   5:e1d36b91fb4a
changeset:   4:80257d1256a9
changeset:   3:6c81277c9392
changeset:   2:82714a5663d1
changeset:   1:89d45b024c67
changeset:   0:51750d40d056

$ cd ..\trunk
$ hg log -r 29:25 | grep changeset
changeset:   29:536184bf7e16
changeset:   28:ec176d410993
changeset:   27:d6ddd117926d
changeset:   26:1f89e8573172
changeset:   25:7b0847a67a80

Si bien hay varias semejanzas con respecto a los meta-datos, las diferencias son notables. Por lo visto hay menos revisiones y ... ¿dos ramas? ¿De dónde salió trunk si clonamos a add_opts? ¿Qué se puede deducir después de ejecutar los comandos anteriores? Primeramente, como las revisiones tienen el mismo identificador en ambos repositorios (trunk y add_opts) se puede deducir que se clonó el primer repositorio que teníamos localmente hasta la revisión 29:536184bf7e16. De ahi en adelante hgsvn detectó que en el repositorio SVN se había hecho una copia de los contenidos de la carpeta trunk y por tanto interpreta este hecho como la creación de una nueva rama y lo refleja localmente. En lo sucesivo solo se añaden las versiones en la rama add_opts. Es por esto que solo se crea una cabeza (a.k.a. head). La misma situación se repite para los otros repositorios.

Si Usted prefiere tener varias ramas en un solo repositorio (como yo ;o) solo resta unir todas las ramas.

$ pwd
/home/olemis/repos/flioops/trunk
$ find ../* -type d -print0  | xargs -0 -I repos hg pull repos
$ hg branches
ochk_arch                     76:6ac33679d5c3
guarded_synch                 75:386b8f866218
test_pycontract               73:d80a5ccc6153
mdl_inv                       69:a9b380084029
pep302                        62:00b7244f3642
add_opts                      60:660d2a6c5f05
pysimrec                      53:93e9562bfb41
trunk                         49:39cfb58b31b6

$ hg heads | grep changeset
changeset:   76:6ac33679d5c3
changeset:   75:386b8f866218
changeset:   73:d80a5ccc6153
changeset:   69:a9b380084029
changeset:   62:00b7244f3642
changeset:   60:660d2a6c5f05
changeset:   53:93e9562bfb41
changeset:   49:39cfb58b31b6

Ya tenemos el repositorio con todas las de la ley. Lo único que es de resaltar es que desaparecen las marcas que indicaban los números de revisión SVN para las ramas, pues son locales a los repositorios clonados.

Conclusiones

Hgsvn solo clona una rama a la vez. Subversion no tiene implementada una noción de ramas, por lo tanto el comando hgimportsvn considera como nombre el último tramo de la URL especificada. La herramienta se destaca por mantener la mayor fidelidad posible con respecto al repositorio SVN original y recuperar la mayor cantidad posible de meta-datos para incorporarlos en la copia local hecha con Mercurial. Hgsvn analiza las operaciones realizadas en el repositorio SVN original e infiere las relaciones entre las ramas para reflejarlas en el clon que se crea localmente.

1 comentario: