Umm me he dado cuenta de que mi entrada anterior es bastante común. Hay muchos otros lugares en inglés y castellano donde puede verse como hacer un simple git svn clone y que explican muy por encima como funciona. De manera que voy a poner un ejemplo real de uso práctico.
¿Cómo estaba usando el repositorio?
Tal y como comenté al final de la entrada anterior, no podemos mandar desde git al svn commits resultado de un merge entre branches. De manera que para poder hacer merges y operar con git habitualmente, decidí tener un branche llamado ‘svn’ que mantendría un historial lineal, actualizado, pero libre de merges. Lo haría haciendo rebases a partir de un master actualizado.
Espera, no me entero de nada ¿Cuál era el problema con git svn dcommit?
Bueno… en git se puede trabajar en varios branches (ramas) diferentes, y luego unir el resultado, fusionarlos, merge. Esto lo que hace es seguir el historial de las dos ramas hasta que tienen un ancestro común, y las une en un commit nuevo, que es lo que llamamos un merge commit.
Pues bien, el problema viene en el momento en que svn maneja los conflictos y los merges de manera diferente, así que enviar un merge commit a un repositorio svn, es buscarse problemas. La solución alternativa a merge en git, es rebase. Rebase lo que hace es, fusionar dos branches, de manera que al final no es que se unan mediante un commit que resuelve las diferencias, sino que a cada commit anterior de uno de los branches, se le aplican todas las diferencias que tiene con la otra, reescribiendo su historia. Al final de un rebase, acabas con el branche desde la que lo hiciste con sus commits reescritos añadiendo lo que tenía el otro branche, es como si lo hubieses escrito así desde siempre.
Esto de andar reescribiendo la historia, es decir aplicando parches hacia atrás, es peligroso cuando se trabaja también con un repositorio remoto, ya que si se modifican commits que ya han sido enviados, y alguien cambia la historia, va a odiarte mucho porque tendrá problemas y muchos conflictos en commits antiguos. Por eso en definitiva me parece mejor ir haciendo merge en master y otras ramas, y separar los rebases en una rama aparte específica para svn.
Hala, seguro que si te manejabas con git, esto ya lo sabías, y probablemente mejor (yo aún soy novatillo).
Trabajando de esta guisa: 3 branches
En el repositorio estoy trabajando con 3 branches:
- master: El normal. Solo lo actualizo con versiones estables.
- work: Lo uso para ir trabajando y haciendo modificaciones.
- svn: Se usará para sincronizar con el svn
El ‘workflow’ será trabajar en work hasta que tenga algo que funcione, hacer un merge con master para actualizarlo. Y una vez que tengo algo decente en master, hacer un rebase desde svn y git svn dcommit para sincronizarlo con el servidor svn.
Un ejemplo real
Partimos de un master, work y svn limpios, sincronizados, todos iguales en su último commit (svn tiene historial diferente).
En este contexto, entramos en nuestro branch de trabajo, work y añadimos una pequeña feature a idiginBPEL.
$ git checkout work $ vim idgui/proyecto.py
Modificamos un par de funciones
$ git diff (.. 20 o 30 líneas de cambios ..) $ git status # On branch work # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout -- ..." to discard changes in working directory) # # modified: idgui/proyecto.py
Ahora hacemos commit del cambio
$ git commit -am "Añadido contador de casos seleccionados a la pantalla de casos" [work f70e216] Añadido contador de casos seleccionados a la pantalla de casos 1 files changed, 10 insertions(+), 5 deletions(-)
Y estamos 1 commit por delante de master. Vamos a sincronizarlos. Para ello nos llegamos a master, y pedimos mezclarlo con work
$ git checkout master Switched to branch 'master' $ git merge work Updating 6832ef9..f70e216 Fast forward trunk/idgui/proyecto.py | 15 ++++++++++----- 1 files changed, 10 insertions(+), 5 deletions(-)
Ha sido fácil, pues ha sido fast-forward, solo tenido que mover el HEAD. Ahora tendríamos código estable en master, vamos a hacer commit también desde svn. Para esto, necesitamos sincronizar también la rama svn, pero como no podemos hacer merge, haremos rebase:
$ git checkout svn Switched to branch 'svn' $ git rebase master First, rewinding head to replay your work on top of it... Fast-forwarded svn to master.
Bien, ya tenemos work, master y svn con HEADS iguales y svn con código actualizado. Vamos a mandar un buen commit al svn
git svn dcommit Committing to https://forja.rediris.es/svn/cusl4-idigin ... Authentication realm: Subversion User Authentication Password for 'frajasal': M trunk/idgui/proyecto.py Committed r245 M trunk/idgui/proyecto.py r245 = 1ded21df0c8c764605d9b505c9ca6738609aaf53 (git-svn)
¡Listo! Ya tenemos todo bien sincronizado
En teoría, si mantengo estrictamente el workflow y siempre trabajo en un branch aparte para después hacer merge con master e inmediatamente rebase con svn, no debería haber problemas de reescritura de commits anteriores en el svn, ya que master y svn irían avanzando al mismo tiempo.
Espero que le sirva a alguien
Actualización: Como me comenta Antonio García en el comentario al post anterior, existe git merge –squash que lo que hace es crear un commit enorme con los cambios necesarios para unir dos ramas, sin información de merge. Esto sería menos peligroso que ir haciendo svn rebase, ya que no hay posibilidad de reescribir commits ya enviados en la rama svn, pero significa abandonar definitivamente la posibilidad de mantener historiales similares en svn y git, ya que git merge –squash no replica de manera parecida los commits de otras ramas, sino que crea un solo commit gordo y hermoso, que habría que mandar con un buen mensaje al svn.