Cómo migrar la historia de un repo SVN a GIT (avanzado)

svn-vs-git

Estos fueron los pasos que se siguieron para hacer un mirror de SVN a GIT.

Para este ejemplo, suponemos un proyecto en SVN, llamado svn_project, que queremos copiar a git hacia una versión que bautizaremos como awesome_project, porque aunque en principio será el mismo proyecto, se vuelve más awesome por estar en GIT.

Nuestro repositorio svn para svn_project está en:

svn://svn.oldserver.com/svn_project con tags trunkproduccion y estable.

Nuestro repositorio en Git lo vamos a hacer en GitHub (hay que hacerse una cuenta gratuita primero) y en nuestro ejemplo se accede a él mediante

git@github.com:amenadiel/awesome_project.git.

Un poco más adelante le haremos 3 ramas análogas a las del proyecto svn que llamaremos svnrepotrunksvnrepoproduccion y svnrepoestable para no olvidar de dónde vienen.

Configurando Repositorio GIT

Esto puede hacerse pasándole la url ssh o la https. La gracia de lo primero es que si se ha autorizado la llave openssh no pedirá login ni password. El cliente GUI de Github para windows es capaz de recordar user y pass también para https.

Este tutorial considera que estamos parados en /var/www/git por lo que el repo se clonará en /var/www/git/awesome_project

Estamos creando un directorio para el repo e inicializando en él un repo de github apuntado a la url (puede ser ssh o https) del proyecto en github. El mismo comando luego trae una copia del branch master del repo awesome_project a un branch local que recibe el mismo nombre master.

Si nos movemos a /var/www/git/awesome_project, el comando -git branch -a- mostrará qué tenemos en este momento en nuestro repo local:

El paso siguiente es generar un branch GIT local para cada uno de los tags SVN que queremos copiar, y de pasada asegurarse de que cada rama local que vamos creando tiene su contraparte en Github a la cual podremos después subir los cambios. La opción -f va a forzar la recreación de cada branch si es que existe. –set-upstream hace lo mismo que –track pero en sentido inverso, relaciona un branch remoto que recibirá los push de cada branch local.

Esto implica que al hacer git pull o git push cada branch local tira y empuja -respectivamente- del branch remoto de  git@github.com:amenadiel/awesome_project.git   al cual quedó asociado.

  • Se regenera el branch master se asocia a origin/master
  • Se genera el branch svnrepotrunk que se asocia a origin/svnrepotrunk
  • Se genera el branch svnrepoproduccion que se asocia a origin/svnrepoproduccion
  • Se genera el branch svnrepoestable que se asocia a origin/svnrepoestable

Podemos ver cómo quedó el repo local usando git remote show origin:

Clonando Repositorio SVN

Ahora vamos a hacer el equivalente a un svn checkout de los repos de svn_project desde svn pero sobre nuestro checkout de git.

Le estamos diciendo que:

  • debe crear una copia del repo  SVN svn_project  en el directorio actual
  • Debe traer desde éste traer el trunk y los tags (en nuestro caso, estable y producción)
  • Opcionalmente se puede incluir el parámetro -r para especificar una revisión, en la forma -r 7865:HEAD que significaría “desde la 7865 a la actual” en todos los repositorios afectados (en este caso trunk y tags).

Si no se le especifica revisión el proceso completo puede tomar horas, porque va a mapear cada commit de svn a uno de git. Si todo va bien esto hace falta realizarlo una sola vez y luego basta con ir actualizando.

Al final, en el archivo .git/config se habrá creado un svn-remote llamado git-svn y habrá generado referencias remotas.

Hasta este momento, hemos descargado el repo de git, dejamos branches locales vinculadas a branches remotas, y ahora pisamos los archivos con los que estan en SVN. A continuación vamos a recoger basura con el garbage collector para ahorrar tiempo más adelante (se junta mucha basura). Este paso es opcional.

Ahora viene la parte que no se menciona casi en ningun tutorial: hay que apuntar cada branch local a un remoto svn, pues ahora están apuntando a un remoto git, y esto se hace reseteando cada branch (equivale más o menos a un svn revert) referido al respectivo remoto svn.

Cada uno de estos pasos equivale a forzar la regeneración de cada branch local referenciándolo a un svn remote (trunk, producción y estable). Al principio del proceso se hace checkout a svnrepoestable para liberar el lock sobre master y poder resetearlo.

Empujando hacia GitHub

Recopilando, hasta ahora hemos hecho lo siguiente: 1. Generamos un repositorio local de Git1.

  1. Lo vinculamos al repositorio Git de awesome_proyect
  2. Generamos las branches locales para trunk, producción y estable
  3. Las vinculamos y sincronizamos con lo que hay en GitHub
  4. Inicializamos un repositorio local de SVN
  5. Sincronizamos ese repositorio de SVN a las referencias locales
  6. Reseteamos la referencia HEAD de nuestras branches GIT locales para coincidir con las de SVN

En teoría en el punto 6 nos sincronizamos con SVN, descargando grueso de los archivos y mapeando cada commit SVN a un commit de GIT, pero esos commits no son archivos físicos sino referencias 1:1 entre una revisión SVN y un hash de GIT. Ahora hay que traer los archivos fisicos. Gracias al punto 7, cada svn-remote sabe que debe afectar al branch trunk, producción o estable.

El paso final es empujar (push) todo lo que se descargó de SVN al repo de GIT. Es posible que mientras toda la organización trabajaba en SVN alguien haya hecho algún cambio en GIT (una propiedad, un submódulo, etc) y GIT rechazará el push diciendo que es anterior al master remoto. A nosotros nos interesa que el remoto GIT sea una copia fiel de SVN, por lo que forzaremos la subida con el flag -f. El flag -u probablemente es redundante, pero rectificará el upstream branch en caso de que se haya desconfigurado al resetear cada branch local.

Opcional: Sincronizando SVN a GIT de ahí en adelante

Los pasos que se han mostrado han de realizarse una sola vez. De ahí en adelante entendemos que se migró toda la historia SVN a GIT y se trabajará solo en GIT, que nadie hará más commits sobre el repo SVN.

Si esto no ocurre, y alguien hace cambios sobre SVN, habría que repetir lo pasos para importar esos cambios a GIT.

Como experimento, ahora estamos en condiciones de aprovechar lo mejor de ambos mundos. Lo que haremos para mantener GIT sincronizado con SVN aunque se hagan cambios en GitHub es descargar los cambios con** git pull**, y luego aplicar git svn rebase para retroceder los cambios de git, aplicar los de svn y encima de ellos aplicar los de git.

El haber introducido un git pull en esta secuencia ya la hace bidireccional de Github al directorio local. Puede que al hacer el svn rebase se generen conflictos de merge. En caso de que no ocurran, se puede commitear y empujar los cambios.

Si hay conflictos git no debiese dejar commitear ni menos empujar los cambios.

Opcional para Gente muy Arriesgada: Sincronizando GIT a SVN de ahí en adelante

OJO Esta sección se incluye a modo puramente teórico y su práctica está absolutamente desaconsejada OJO

Una vez que la organización se haya mudado a Git, puede ser deseable mantener los SVN actualizados como espejo de Git. Esto también se puede haciendo:

Esto debe hacerse con sumo cuidado porque se estará empujando a trunk y a los tags producción y estable lo que se encuentre en master y en los branches del repositorio Git.

Links de Interés

Leave a Reply