domingo, 28 de febrero de 2016

git es como svn (I)


Bugatti Veyron 16.4 – Frontansicht (1), 5. April 2012, Düsseldorf.jpg
Fuente de la Imagen: Wikipedia

Como están?

Desde hace unos 8 años he estado usando controladores de versiones distribuidos. En un principio utilicé bzr y hace unos 3 años probé git y no hubo forma de volver la vista atrás.

Llevo casi 2 años trabajando en un proyecto relativamente grande donde se usa svn para controlar las versiones. Desde hace unos 8 meses he estado usando git-svn para poder utilizar git por mi cuenta e integrar los cambios en svn (esto luego de poner "bajo control" la configuración de git para que no jodiera los saltos de línea de los archivos)

De vez en cuando surge el punto conversando con cualquier desarrollador acerca de los controladores de versiones (incluso desarrolladores veteranos de la 3a guerra mundial) y sigue sorprendiéndome la frase:

- "pero git y svn hacen lo mismo".

Normalmente cuando la escucho y pongo mi cara de "no puedo creer esta mierda viniendo de X" (X siendo el desarrollador de marras) entonces la frase se convierte luego de unos segundos de silencio en:

- "pero git y svn hacen lo mismo..... no?"

Pues sí..... hacen lo mismo. En más o menos el mismo sentido en el que un Volkswagen Escarabajo hace lo mismo que un Bugatti Veyron Super Sport. Ambos son carros no? Si, son carros... Ambos te pueden llevar de un lado al otro, pero es obvio que uno es más carro que otro... o no? (dejando aparte el hecho de definir cual considerarían ustedes más carro que el otro, queda claro que son carros MUY diferentes).

Las ventajas son difíciles de ver "en el aire" mientras estamos conversando así que normalmente hacer que las personas capten las diferencia requiere que las siente en una silla (normalmente en contra de su voluntad, como en La Naranja Mecánica) y vean con sus propios ojos las cosas que puede hacer git que svn solo podría hacer en sueños lúcidos.

The Garcia Effect Nitpicks A Clockwork Orange So You Don't Have To
Image source: Gizmodo (espero no estarme metiendo en problemas por usar esta imagen :-))

Voy a intentar resumir algunos de los problemas que yo puedo afrontar utilizando git que mis compañeros de trabajo no pueden por utilizar svn (o sí pueden, pero la técnica para hacerlo es engorrosa) mientras trabajamos en nuestro proyecto usando svn así que el enfoque sigue siendo para colocar los cambios en un servidor centralizado y no para explorar todas las posibilidades que nos brinda git.

Exposición de interés: recientemente me convertí en un (muy muy) pequeño contribuidor de git con unos parches para generar salida de progreso (como la que van a ver un poco más abajo cuando hago git blame). No tengo afilicación con el proyecto svn o con algún otro proyecto apache (hasta lo mejor de mi conocimiento).

1 Trabajar sobre varias ramas desde una sola copia de trabajo

Quién ha tenido que trabajar sobre varias ramas de un proyecto al mismo tiempo? Y cuantos de ustedes mantienen una copia de trabajo por cada una de esas ramas? En este caso voy a elucubrar diciendo que la mayoría. Saben que existe la operación "svn switch" que les permitiría saltar de una rama a la otra sin necesidad de tener que crear una nueva copia de trabajo en su equipo cierto? Sí, es probable que la mayoría de los desarrolladores que utilizan svn lo sepan. Y entonces... por qué no la usan? Ah, porque saltar de una rama a la otra tarda mucho tiempo, cierto? Exacto. Pero considerarían utilizar svn switch si ese salto solo tardara algunos segundos sin importar qué tan "lejos" vayan a saltar? Excelente. Les presento "git checkout" que hace exactamente eso.

Hagamos una comparación:

~/proyectos/svn/svn  
15:20 $ svn info
Path: .
Working Copy Root Path: /home/antoranz/proyectos/svn/svn
URL: https://svn.apache.org/repos/asf/subversion/trunk
Relative URL: ^/subversion/trunk
Repository Root: https://svn.apache.org/repos/asf
Repository UUID: 13f79535-47bb-0310-9956-ffa450edef68
Revision: 1732667
Node Kind: directory
Schedule: normal
Last Changed Author: stsp
Last Changed Rev: 1732400
Last Changed Date: 2016-02-25 17:41:02 -0600 (Thu, 25 Feb 2016)

15:21 $ git show --summary
commit 95e34ade485c774b580ef29b0560b12750f1eb5b (HEAD -> trunk, origin/trunk, origin/HEAD)
Author: Stefan Sperling
Date:   Thu Feb 25 23:41:02 2016 +0000

   * subversion/libsvn_client/conflicts.c
     (conflict_type_specific_setup,
      conflict_tree_get_description_incoming_delete): Remove unused variables.
  
   Found by: philip
  
  
   git-svn-id: https://svn.apache.org/repos/asf/subversion/trunk@1732400 13f79535-47bb-0310-9956-ffa450edef68


Sí, apache mantiene un espejo del repositorio de subversion sobre un repositorio en github. No se molesten en buscar un espejo del repositorio de git montado sobre svn porque no lo van a encontrar.

Cuanto tiempo me llevaría moverme a una rama/revisión relativamente distante?

~/proyectos/svn/svn  
15:30 $ time svn switch --quiet https://svn.apache.org/repos/asf/subversion/tags/1.0.0

real    0m5.020s

user    0m1.012s
sys     0m0.640s


Nada mal.... 5 segundos para saltar a la etiqueta 1.0.0. Había hecho una prueba antes y me llevó 13 segundos... pero no importa. Veamos cuanto le lleva a git hacer el mismo salto:

~/proyectos/svn/git [trunk|]  
15:28 $ time git checkout 1.0.0
Checking out files: 100% (3012/3012), done.
Note: checking out '1.0.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

 git checkout -b

HEAD is now at 0844db9... Add tag 1.0.0

real    0m0.345s

user    0m0.232s
sys     0m0.096s


5 segundos vs 345 milisegundos. Ahora piensen en los (probablemente) minutos que lleva saltar de una rama a la otra en su proyecto y piensen en cómo podría mejorar su propio desempeño en el trabajo si los saltos se pudieran hacer en solo segundos o fracciones de segundo en vez de minutos. Probablemente dejarían de utilizar una copia de trabajo por rama y trabajarían en un sólo árbol de trabajo (el término en la jerga de git para referse a la copia de trabajo en svn).

2 No se necesita una conexión al servidor para poder trabajar

En este punto les presento otra de las ventajas de usar un controlador distribuído. Voy a desconectar mi equipo de internet por unos segundos para la próxima demostración. Regresemos al trunk:

~/proyectos/svn/svn  
15:31 $ time svn switch --quiet https://svn.apache.org/repos/asf/subversion/trunk
svn: E170013: Unable to connect to a repository at URL 'https://svn.apache.org/repos/asf/subversion/trunk'
svn: E670003: Temporary failure in name resolution

real    0m0.727s
user    0m0.060s
sys     0m0.012s


Ok, problemas de conexión al servidor svn. Y en git?

~/proyectos/svn/git [1.0.0|]  
15:29 $ time git checkout trunk
Checking out files: 100% (3012/3012), done.
Previous HEAD position was 0844db9... Add tag 1.0.0
Switched to branch 'trunk'
Your branch is up-to-date with 'origin/trunk'.

real    0m0.881s
user    0m0.524s
sys     0m0.132s


Git mantiene una copia completa del repositorio de forma local. Y no empiecen a hablar paja del espacio que esto implica. El repositorio del clon de subversion pesa unos 220 MBs para las 76000+ revisiones lo que implica un promedio de unos 3k por revisión y con los GBs y TBs de los que se habla en estos días yo estaría dispuesto a hacer el sacrificio de espacio (si es tanto espacio como esto) para tener una copia completa del repositorio conmigo a donde vaya (lo cual implica un full backup, por cierto).

Pero esta ventaja aplica no solamente para un switch/checkout. Básicamente todas las operaciones que se hacen sobre una copia de trabajo requieren de una conexión al servidor en svn (excepto un revert, quizás?).

~/proyectos/svn/svn  
15:44 $ svn annotate dist.sh
svn: E170013: Unable to connect to a repository at URL 'https://svn.apache.org/repos/asf/subversion/tags/1.0.0/dist.sh'
svn: E670003: Temporary failure in name resolution


~/proyectos/svn/git [1.0.0|]  
15:45 $ time git blame dist.sh > /dev/null

real    0m0.199s
user    0m0.200s
sys     0m0.000s


~/proyectos/svn/git [1.0.0|]  
15:46 $ time git log > /dev/null

real    0m0.254s
user    0m0.248s
sys     0m0.004s


~/proyectos/svn/svn  
15:47 $ svn log
svn: E170013: Unable to connect to a repository at URL 'https://svn.apache.org/repos/asf/subversion/tags/1.0.0'
svn: E670003: Temporary failure in name resolution


Sin conexión al servidor su copia de trabajo vuelve temporalmente a la época de las cavernas. En el caso de que tengamos conexión de red, la ventaja de trabajar local se vuelve a ver reflejada en la velocidad a la cual se pueden hacer las operaciones. Al igual que el checkout de git fue mas de un órden de mágnitud más rápido que el svn switch, muchas otras operaciones se verán afectadas por el tema de la latencia de la conexión al servidor. Me vuelvo a conectar y voy a pedir un log: (que ya vimos que le tomó 254 milisegundos generarlo a git sobre la etiqueta 1.0.0 de subversion):

~/proyectos/svn/svn  
15:52 $ time svn log > /dev/null

real    0m12.150s
user    0m0.512s
sys     0m0.024s


(Esta fue la mejor de 3 pruebas. La que más se tardó llegó a 30+ segundos). Si lo hacemos desde trunk (que tiene una historia mucho más larga):

~/proyectos/svn/svn  
15:54 $ time svn log > /dev/null

real    2m42.288s
user    0m2.036s
sys     0m0.076s

~/proyectos/svn/git [trunk|]  
15:59 $ time git log > /dev/null

real    0m1.777s
user    0m1.292s
sys     0m0.040s


2+ minutos vs 1.777 segundos. En este caso fue una diferencia de casi 3 órdenes de magnitud.

~/proyectos/svn/svn  
15:56 $ time svn annotate subversion/svn/svn.c > /dev/null

real    0m6.247s
user    0m0.688s
sys     0m0.196s


~/proyectos/svn/git [trunk ↓·1|]  
16:01 $ time git blame subversion/svn/svn.c > /dev/null
Blaming lines: 100% (3118/3118), done.

real    0m5.626s
user    0m2.272s
sys     0m0.100s


Ok... este se acercó. 6.247 vs 5.626 (aunque igual svn fue más de 10% más lento que git).

Un diff entre trunk y 1.0.0?

~/proyectos/svn/git [trunk ↓·3|]  
21:04 $ time git diff 1.0.0 > /dev/null

real    0m1.157s
user    0m1.076s
sys     0m0.084s


~/proyectos/svn/svn  
20:57 $ time svn diff --old ^/subversion/tags/1.0.0 --new ^/subversion/trunk > /dev/null
^Csvn: E200015: Caught signal
svn: E200042: Additional errors:
svn: E200015: Caught signal

real    5m18.315s
user    0m3.020s
sys     0m0.720s

(luego de 5 minutos me cansé de esperar por el resultado... y mejor no me pongan a hablar acerca de la sintaxis en la que hay que explicarle a svn cuales son las ramas que quiero comparar porque empiezo a botar espuma por la boca).

3 Puedo crear las ramas que me venga en gana

Sí, como lo escuchan (leen?). Como yo soy el dueño y señor de mi repositorio local, eso quiere decir que yo puedo crear todas las ramas que me de la gana crear sin necesidad de esperar a que se creen en el repositorio central. Y entonces normalmente se escucha la pregunta (y normalmente con el tonito más fastidioso que haya conocido ser humano alguna vez): "Pero pa'quéeeeeee????!!!!".

Cuantos de ustedes habrán tenido que trabajar en varias soluciones (pensemos en tickets, quizás) al mismo tiempo sobre el proyecto? En los repositorios donde usan subversion normalmente no se tomarían la molestia de crear una rama por ticket (aunque es técnicamente posible, que quede claro). Por qué no? Supongo que es porque es demasiado trabajo para los administradores (aunque podría haber otras razones mas técnicas que escapan a mi entendimiento/conocimiento/comprensión de cómo trabaja svn). Si este es el caso, entonces se utiliza una sola rama de desarrollo donde todos los desarrolladores van apilando sus cambios. Pero si yo estoy desarrollando varios cambios al mismo tiempo eso quiere decir que los voy a tener todos "regados" sobre mi copia de trabajo mientras no los acometa (y no me van a engañar haciéndome creer que tienen una copia de trabajo por cada ticket, verdad que no?). Eso significa que tengo que ser bastante cuidadoso para no acometer cambios que pertenecen a un ticket Z cuando quiero acometer los cambios de un ticket X. Eso entonces significa que tengo que ser bastante cuidadoso (más de lo normal, quiero decir) a la hora de acometer los cambios. El caso en el que se tiene cambios flotando de varios tickets sobre un solo archivo lo voy a nombrar para ilustrar un problema adicional (voy a acometer los cambios del ticket X? Tengo que comentar o sacar por unos segundos los del ticket Z. Luego de acometer los del ticket X puedo poner el archivo como estaba).

Hay una forma al alcance de los desarrolladores que usan subversion para poder "manejar" esa situación: Utilizar parches para poder limpiar la copia de trabajo cuando se desea deja de trabajar en un ticket X y comenzar a trabajar en un ticket Y.

Digamos que estoy trabajando en el ticket X y no está listo aún para acometerse y me solicitan trabajar inmediatamente sobre el ticket Y.
1 creo un parche para el trabajo que tengo actualmente para el ticket X. 2 Revert 3: comienzo a trabajar en el ticket Y.

Si deseo regresar a trabajar al ticket X (sin haber acometido los cambios del ticket Y):
1 creo un parche para el trabajo que tengo actualmente para el ticket Y. 2 Revert 3: aplico el parche del ticket X. 4 Sigo trabajando en el ticket X.

Este manejo de los parches "offline" (quiero decir, fuera de svn) se puede volver bastante engorroso (se puede volver? ES!). Incluso creo que hay IDEs que le dan soporte al manejo de los parches para facilitarle la vida a los desarrolladores. De cualquier forma, el manejo de estos parches no es algo integral al core de svn.

Ahora supongamos que nuestro administrador del repositorio svn es suficientemente benévolo como para crear una rama para cada ticket. En este momento la vida se vuelve mucho más bella: Podemos hacer svn switch entre las ramas para poder trabajar sobre cada uno de los tickets de forma independiente y así podemos manejar los cambios para cada ticket de forma separada. Cuando un ticket está listo (luego de probablemente varias acometidas, dependiendo de la dificultad de desarrollarlo/resolverlo) se podría mezclar dicho ticket a la rama principal de desarrollo (o llevar los cambios con un svn merge -c, mejor conocido en los bajos fondos como cherry-pick). Si pueden visualizar eso entonces imaginen la situación donde ustedes sean los administradores del repositorio svn y no le tengan que pedirle permiso a nadie para crear las ramas. La vida no podría ser más bella, cierto? Bueno, eso es lo que sucede cuando trabajan con git sobre su repositorio local. Ustedes pueden crear las ramas que necesiten (una por ticket, por ejemplo) para desarrollar cada ticket de forma independiente sin necesidad de esperar que nadie haga ese trabajo por ustedes.

Por ejemplo, en el proyecto donde trabajo todavía no hemos entrado a la fase de desarrollo (comienza dentro de unas 2 semanas) y no tenemos ni si quiera una rama de desarrollo donde ir colocando los cambios, ni aunque los tuviéramos listos (y en mi proyecto no van a crear una rama por ticket, lo tengo muy claro). Yo en este momento estoy desarrollando (adelantándome a los acontecimientos) 4 tickets de variada dificultad sobre la rama de pruebas del release anterior al que voy a estar trabajando utilizando una rama separada por cada ticket. Cuando me inspiro para trabajar en un ticket hago un git checkout a esa rama, hago las acometidas que requiera y luego cuando quiero trabajar en otro ticket simplemente hago un git checkout de dicha rama (que como ya vimos es una operación bastante rápida) y continúo trabajando en dicho ticket. La vida es bella! O dicho en términos de Costa Rica, que es donde estoy viviendo hace casi dos años: PURA VIDA!!!!

Esto también aplica para poder desarrollar varias ideas de un solo ticket. Digamos que tienen 2 ideas diferentes para hacer el mismo trabajo (el mismo ticket). Si trabajamos con svn podríamos, igual que había dicho antes, implementar una idea, crear un parche, revertir, implementar la otra idea y crear un parche para la misma. Cuando quiero probar una idea, aplico el parche de dicha idea, pruebo, limpio. Cuando quiero probar la otra idea aplico el parche, pruebo, limpio. Pero no sería mucho más sencillo crear 2 ramas (con todas las ventajas desde el punto de vista de control de versiones que conlleva usar ramas separadas) y saltar entre una rama y la otra?

Pero esto no solo aplica para Tickets. Imagínen que tienen en su cabeza un cambio arquitectónico de la aplicación (o un módulo de la misma) y que crear una prueba de concepto para ello requeriría de varios días o semanas. Para una prueba de concepto va a ser difícil que consigan una rama en el repositorio (especialmente si es una prueba de concepto, digamos, extraoficial). Pero varios días de trabajo implicarían un cambio probablemente monumental que a la hora de revisarlo sería difícil para cualquier persona abordarlo de un solo golpe en un parche. Si pudieran tener una rama para poder ir haciendo los cambios de forma paulatina acometiendo cambios más pequeños sin duda sería más fácil de asimilarla por las personas que tuvieran que revisarla/analizarla al ver el trabajo que requirió cada paso.

Resumen: Luego de utilizar ramas locales no van a querer sentarse a trabajar sin ellas en su vida.

Conclusión
Quien dijo que no podríamos recorrer Nürburgring en un Escarabajo? Nadie! Pero cuantos preferirían hacerlo en el Bugatti?

Hasta luego!


PS: Hay otras muchas (muchísimas ventajas) de utilizar controladores distribuidos vs centralizados pero en este artículo me enfoqué en las cosas que yo puedo hacer con git mientras trabajamos en un proyecto que está usando svn para controlar versiones así que no iba a ahondar en temas como push vs pull, los flujos de trabajo con gatekeeper etc etc. Para las ventajas/diferencias entre los dos modelos (centralizado vs distribuido) escribiré otro artículo (quien sabe cuando... no se queden aguantando la respiración esperando por él).