25 enero 2014

Mantén a salvo tu código (tutorial de Mercurial)

En uno de mis artículos anteriores escribía acerca de algunas de las opciones disponibles para guardar versiones del código conforme este evolucionaba. Analizamos las principales opciones utilizadas por los desarrolladores freelance: Git y Mercurial, y los principales proveedores en la nube para ellos: GitHub y Bitbucket. Mi conclusión fue que, dado que desarrollo principalmente en Python, mi elección lógica era Mercurial y Bitbucket, mayoritaria dentro de la comunidad Python. En este artículo vamos a aprender los principales comandos para usar Mercurial y mantener un repositorio en Bitbucket.

Mercurial cuenta con instaladores para Windows, Linux y MacOS. Además, se pueden utilizar herramientas gráficas para gestionarlo (como TortoiseHg) o limitarse a controlarlo desde la consola. En este tutorial vamos a centrarnos en la versión para linux (concretamente la de Ubuntu) gestionada mediante comandos de consola. Usar la consola tiene la ventaja de que es más claro e inmediato explicar los conceptos.

Para instalar Mercurial basta con teclear:

$ sudo aptitude install mercurial

Una vez instalado, se puede ejecutar como un usuario normal sin privilegios, pero antes se debe hacer una configuración mínima: en la raíz del directorio home del usuario se debe crear un fichero llamado ".hgrc" (ojo al punto inicial. Este fichero sirve para fijar las variables globales utilizadas por Mercurial. La configuración mínima necesaria para Mercurial es el usuario y la dirección de correo que se quiera usar para marcar cada actualización del repositorio. En mi caso, el fichero tiene el siguiente contenido:
$ cat .hgrc [ui] username = dante [extensions] graphlog= $

Con  cambiar el contenido por el nombre y el correo de cada una ya está, esa es toda la configuración que necesita Mercurial para funcionar. La parte del Graphlog nos permitirá mostrar información muy útil cuando expliquemos las ramas (branches), algo más tarde en este artículo.

Ahora vayamos a la carpeta donde tenemos el código fuente que queremos controlar y le diremos a Mercurial que cree allí el repositorio. Supongamos que el fichero de código fuente se llama sólo "source" y que su contenido es:
source$ ls source$

Para crear ahí un repositorio de Mercurial basta con hacer:
source$ ls source$ hg init source$ ls source$

Espera, ¿no ha cambiado nada?, ¿esto es normal?. En realidad sí porque Mercurial esconde su directorio de trabajo para protegerlo de borrados accidentales:
source$ ls -la total 12 drwxrwxr-x 3 dante dante 4096 ene 17 22:11 . drwxrwxr-x 6 dante dante 4096 ene 17 22:09 .. drwxrwxr-x 3 dante dante 4096 ene 17 22:11 .hg source$

Ahí está, el directorio ".hg" que usa Mercurial. Dentro de él, Mercurial guardará las versiones de nuestro ficheros de código. Mientras la carpeta ".hg" esté a salvo también lo estará nuestro código.

Con "hg status" podemos ver qué pasa en nuestro repositorio. Si lo tecleamos con un repositorio recien creado en una carpeta que aún no tenga ficheros, "hg status" devolverá una respuesta vacía:
source$ hg status source$

Sin embargo, si creamos dos ficheros:
source$ touch code_1.txt source$ touch code_2.txt source$ ls code_1.txt  code_2.txt source$ hg status ? code_1.txt ? code_2.txt source$

Esas dos interrogaciones de la salida de "hg status" nos dicen que Mercurial ha detectado dos ficheros en la carpeta que todavía no están incluidos en el repositorio. Para añadirlos debemos hacer:
source$ hg add code_1.txt source$ hg add code_2.txt source$ hg status A code_1.txt A code_2.txt source$
Ahora las interrogaciones han cambiado a "A" lo que significa que esos ficheros acaban de añadirse al repositorio. Esta vez hemos añadido los ficheros uno a uno, pero podríamos haberlo hecho de una vez haciendo solamente "hg add .". También podríamos usar comodines en este comando. Además podemos crear listas de exclusión si creásemos el fichero ".hgignore" dentro de la carpeta del fichero fuente. De esta manera podemos ajustar en detalle qué ficheros se incluirán en el repositorio de Mercurial y cuales no. Por ejemplo, lo normal es tener en el repositorio ficheros textuales de código fuente, no ficheros compilados (de ese código fuente) o bases de datos de prueba que puedan ser regeneradas fácilmente. Lo mejor es guardar en el repositorio sólo los ficheros que realmente se necesiten con el fin de mantener el tamaño del repositorio lo más pequeño posible. Hay que tener en cuenta que si alojamos nuestro repositorio en Bitbucket (u otro alojamiento de código fuente), seguramente tendremos un límite de tamaño máximo para nuestro repositorio en la nube si queremos seguir como usuarios gratuitos.

Los cambios en el repositorio no serán válidos hasta que los validemos con "hg commit":
source$ hg commit -m "Two initial files just created empty." source$ hg status source$

El parámetro "-m" en el "hg commit" nos permite comentar la versión de manera que podamos saber de un vistazo qué cambios contiene. Una vez que el cambio es validado desaparece de "hg status", esa es la razón por la que en nuestro anterior ejemplo vuelve a salir vacío de nuevo. Si modificamos uno de los ficheros:
source$ hg status source$ echo "Hello" >> code_1.txt source$ hg status M code_1.txt source$

La "M" en la salida de hg status significa que Mercurial ha detectado que un fichero incluido en el repositorio ha cambiado con respecto a la versión que había registrada. Para incluir dicha modificación en el repositorio tenemos que validar el cambio:
source$ hg commit -m "Code_2 modified." source$ hg status source$

¡Pero un momento! ¡espera! ¡hemos cometido un error!, el mensaje es incorrecto porque el fichero modificado es code_1 no code_2. Mercurial nos permite corregir la última validación con el parámetro "--amend":
source$ hg log changeset:   1:4161fbd0c054 tag:         tip user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_2 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ hg commit --amend -m "Code_1 modified." saved backup bundle to /home/dante/Desarrollos/source/.hg/strip-backup/4161fbd0c054-amend-backup.hg source$ hg log changeset:   1:17759dec5135 tag:         tip user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_1 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$

"hg log" muestra el histórico de validaciones. A través de ese histórico podemos ver que el mensaje de la última actualización ha sido corregido gracias al parámetro "--amend". Sin embargo, con ese parámetro sólo se puede arreglar la última validación. Cambiar validaciones más antiguas se considera peligroso y no hay una manera fácil de hacerlo (aunque se puede, pero es bastante delicado).

¿Qué pasa si uno se da cuenta de que ya no necesita uno de los ficheros del proyecto y quiere retirarlo del repositorio para que Mercurial no le haga seguimiento?. Una opción sería borrar el fichero de la carpeta del código fuente...
source$ ls code_1.txt  code_2.txt source$ rm code_2.txt source$ ls code_1.txt source$ hg status ! code_2.txt source$

... pero se puede ver que Mercurial alerta (el mensaje con la exclamación "!") de que no puede encontrar un fichero al que le hace seguimiento por estar en el repositorio. Para decirle a Mercurial que deje de hacerle ese seguimiento a un fichero en particular:
source$ hg status ! code_2.txt source$ hg remove code_2.txt source$ hg status R code_2.txt source$ hg commit -m "Code_2 removed." source$ hg status source$

Con "hg remove" se puede marcar un fichero para ser retirado del repositorio, esa es la razón por la que "hg log" muestra una "R" que significa que el fichero va a ser borrado del repositorio en la próxima validación.

Vale, hemos borrado un fichero del repositorio pero entonces nos damos cuenta de que en realidad lo necesitábamos y que borrarlo fue un error. Tenemos dos opciones. El primero es devolver el repositorio a la última versión en la que el fichero borrado estaba presente, copiarlo a un fichero temporal, devolver al repositorio al últimos estado actualizado y copiar el fichero de la carpeta temporal a la del código:
source$ hg log changeset:   2:88ac7cad647e tag:         tip user:        dante date:        Sat Jan 18 00:39:50 2014 +0100 summary:     Code_2 removed. changeset:   1:17759dec5135 user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_1 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ hg update 1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt source$ cp code_2.txt /tmp/code_2.txt source$ hg update 2 0 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt source$ cp /tmp/code_2.txt code_2.txt source$ hg status ? code_2.txt source$ hg add code_2.txt source$ hg status A code_2.txt source$

Como se puede ver, con el comando "hg update" podemos hacer que nuestra carpeta de código fuente viaje en el tiempo al estado que tenía en una versión concreta. Sólo hay que tener en cuenta que el número que usa "hg update" es el primero que figura en el id de revisión mostrado por "hg log". Por ejemplo, si quisiéramos volver a este estado:

changeset:   1:17759dec5135
user:        dante
date:        Fri Jan 17 23:09:00 2014 +0100
summary:     Code_1 modified.

deberíamos usar "hg update 1" debido a que la versión dice "changeset 1:...", ¿se entiende?.

El problema de este enfoque es que es poco elegante y resulta fácil cometer un error. Un enfoque más directo sería localizar el último estado en el que está presente el fichero a recuperar y traerlo de vuelta con "hg revert":
source$ ls code_1.txt source$ hg status source$ hg log -l 1 code_2.txt changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ hg revert -r 0 code_2.txt source$ hg log changeset:   2:88ac7cad647e tag:         tip user:        dante date:        Sat Jan 18 00:39:50 2014 +0100 summary:     Code_2 removed. changeset:   1:17759dec5135 user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_1 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ hg status A code_2.txt source$ source$ hg commit -m "Code_2 recovered." source$ hg log changeset:   3:9214d0557080 tag:         tip user:        dante date:        Sat Jan 18 01:07:24 2014 +0100 summary:     Code_2 recovered. changeset:   2:88ac7cad647e user:       dante date:        Sat Jan 18 00:39:50 2014 +0100 summary:     Code_2 removed. changeset:   1:17759dec5135 user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_1 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ ls code_1.txt  code_2.txt source$

Lo principal es que "hg log -l 1 code_2.txt" muestra que la última versión en el que el fichero fue modificado. Con esa versión podemos hacer que Mercurial rescate el fichero deseado desde alí. ("hg revert -r 0 code_2.txt). No hay que olvidar realizar una validación al finalizar el rescate.

Ahora elevemos las apuestas. A veces uno quiere intentar desarrollar nuevas funcionalidades para nuestro programa pero no quiere enredar con los ficheros estables. Ahí es donde entran en juego las ramas. Al crear una rama podemos desarrollar sobre una copia aparte de nuestra rama principal (denominada "default"). Una vez que estemos seguros de que la rama está ista para entrar en producción podemos fundir (merge) la rama con la principal, incluyendo los cambios en los ficheros estables de la rama principal.

Supongamos que queremos desarrollar dos funcionalidades, podemos crear dos ramas: "feature1" y " feature2":
source$ hg branches default 0:03e7ab9fb0c6 source$ hg branch feature1 marked working directory as branch feature1 (branches are permanent and global, did you want a bookmark?) source$ hg branches default 0:03e7ab9fb0c6 source$ hg status source$ hg commit -m "Feature1 branch created." source$ hg branches feature1 1:6c061eff633f default 0:03e7ab9fb0c6 (inactive) source$

"hg branches" muestra las ramas del repositorio pero estas no se crean en realidad hasta que se validan con un "hg commit", tras hacer un "hg branch". Esa es la razón por la que el primer "hg branches" del ejemplo anterior sólo muestra la rama principal.
source$ touch code_feature1.txt source$ ls code_1.txt  code_2.txt  code_feature1.txt source$ hg status ? code_feature1.txt source$ hg add code_feature1.txt source$ hg commit -m "code_feature1.txt created"

Para cambiar de una rama a otra hay que usar "hg update":
source$ hg update default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt source$

Cuando cambiamos de una rama a otra los ficheros se crean o borran del directorio para crear el esquema de la versión de la rama.
source$ hg branch feature2 marked working directory as branch feature2 (branches are permanent and global, did you want a bookmark?) source$ hg commit -m "Feature2 branch created" source$ touch code_feature2.txt source$ hg add code_feature2.txt source$ hg commit -m "code_feature2.txt created" source$ ls code_1.txt  code_2.txt  code_feature2.txt source$ hg branches feature2                       7:42123cefb28c feature1                       5:09f18d24ae0e default                        3:9214d0557080 (inactive) source$ hg update default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt source$

Por supuesto podemos seguir trabajando en la rama principal:

source$ ls code_1.txt  code_2.txt source$ touch code_3.txt source$ ls code_1.txt  code_2.txt  code_3.txt source$ hg add code_3.txt source$ hg commit -m "code_3.txt created" source$

Cuando uno trabaja simultáneamente con varias ramas es natural sentirse un poco perdido. Para saber en que rama estamos en cada momento se puede teclear "hg branch" sin nada detrás. Para conseguir una representación gráfica de los cambios validados a las distintas ramas se puede usar "hg log -G":

source$ hg log -G @  changeset:   8:09e718575633 |  tag:         tip |  parent:      3:9214d0557080 |  user:        dante |  date:        Sat Jan 18 20:53:06 2014 +0100 |  summary:     code_3.txt created | | o  changeset:   7:42123cefb28c | |  branch:      feature2 | |  user:        dante | |  date:        Sat Jan 18 20:40:56 2014 +0100 | |  summary:     code_feature2.txt created | | | o  changeset:   6:52f1c855ba6b |/   branch:      feature2 |    parent:      3:9214d0557080 |    user:        dante |    date:        Sat Jan 18 20:39:05 2014 +0100 |    summary:     Feature2 branch created | | o  changeset:   5:09f18d24ae0e | |  branch:      feature1 | |  user:        dante | |  date:        Sat Jan 18 20:22:35 2014 +0100 | |  summary:     code_feature1.txt created | | | o  changeset:   4:2632a2e93070 |/   branch:      feature1 |    user:        dante |    date:        Sat Jan 18 20:20:28 2014 +0100 |    summary:     Feature1 branch created | o  changeset:   3:9214d0557080 |  user:        dante |  date:        Sat Jan 18 01:07:24 2014 +0100 |  summary:     Code_2 recovered. | o  changeset:   2:88ac7cad647e |  user:        dante |  date:        Sat Jan 18 00:39:50 2014 +0100 |  summary:     Code_2 removed. | o  changeset:   1:17759dec5135 |  user:        dante |  date:        Fri Jan 17 23:09:00 2014 +0100 |  summary:     Code_1 modified. | o  changeset:   0:bf50392b0bf2    user:        dante    date:        Fri Jan 17 22:43:34 2014 +0100    summary:     Two initial files just created empty. source$

Para usar el parámetro "-G" con "hg log" hay que incluir las siguientes líneas en el fichero ".hgrc" que mencionábamos al comienzo del artículo:

[extensions]
graphlog=

Una vez que llegamos a la conclusión de que nuestra rama está lo suficientemente madura como para agregar sus cambios a la rama principal, podemos usar "hg merge":
 source$ hg update feature1 1 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt  code_feature1.txt source$ cat code_1.txt Hello source$ echo "World" >> code_1.txt source$ cat code_1.txt Hello World source$ hg status M code_1.txt source$ hg commit -m "code_1.txt modified with world" source$ hg update default 2 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt  code_3.txt source$ cat code_1.txt Hello source$ hg merge feature1 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) source$ ls code_1.txt  code_2.txt  code_3.txt  code_feature1.txt source$ cat code_1.txt Hello World source$


Es importante tener en cuenta que antes de hacer un merge hay que ponerse en la rama donde se quieren insertar los cambios. Una vez allí se llama a "hg merge" con el nombre de rama desde donde se quieren importar los cambios. Por supuesto, los cambios no se incorporan de manera efectiva al repositorio hasta que son validados:
source$ hg status M code_1.txt M code_feature1.txt source$ hg commit -m "Feature1 merged to default branch" source$

Fijémonos en cómo el log graph ha cambiado para mostrar la unión entre ramas:
source$ hg log -G @    changeset:   10:677a88f54dd3 |\   tag:         tip | |  parent:      8:1b93d501259a | |  parent:      9:8b55fb7eec71 | |  user:        dante | |  date:        Sun Jan 19 00:07:54 2014 +0100 | |  summary:     Feature1 merged to default branch | | | o  changeset:   9:8b55fb7eec71 | |  branch:      feature1 | |  parent:      5:197964afe12f | |  user:        dante | |  date:        Sat Jan 18 23:57:03 2014 +0100 | |  summary:     code_1.txt modified with world | | o |  changeset:   8:1b93d501259a | |  parent:      3:132c0505c7b2 | |  user:        dante | |  date:        Sat Jan 18 23:56:24 2014 +0100 | |  summary:     code_3.txt created | | | | o  changeset:   7:86391749b3c3 | | |  branch:      feature2 | | |  user:        dante | | |  date:        Sat Jan 18 23:55:04 2014 +0100 | | |  summary:     code_feature2.txt created | | | +---o  changeset:   6:30decd2ffa21 | |    branch:      feature2 | |    parent:      3:132c0505c7b2 | |    user:        dante | |    date:        Sat Jan 18 23:54:38 2014 +0100 | |    summary:     Feature2 branch created | | | o  changeset:   5:197964afe12f | |  branch:      feature1 | |  user:        dante | |  date:        Sat Jan 18 23:53:43 2014 +0100 | |  summary:     code_feature1.txt created | | | o  changeset:   4:4bbf5ca2e0b6 |/   branch:      feature1 |    user:        dante |    date:        Sat Jan 18 23:52:26 2014 +0100 |    summary:     Feature1 branch created | o  changeset:   3:132c0505c7b2 |  user:        dante |  date:        Sat Jan 18 23:52:02 2014 +0100 |  summary:     Code_2 recovered. | o  changeset:   2:05e0a410c49d |  user:        dante |  date:        Sat Jan 18 23:51:24 2014 +0100 |  summary:     Code_2 removed. | o  changeset:   1:552e1b95fffe |  user:        dante |  date:        Sat Jan 18 23:49:35 2014 +0100 |  summary:     Code_1 modified. | o  changeset:   0:a22ab902f1a7    user:        dante    date:        Sat Jan 18 23:48:55 2014 +0100    summary:     Two initial files just created empty. source$

Cuando se ha acabado el trabajo en una rama y no se planea hacer ninguna mejora más en dicha rama, se puede hacer para cerrar una rama de manera que ya no aparezca en la lista de "hg branches":
source$ hg branches default                       10:677a88f54dd3 feature2                       7:86391749b3c3 feature1                       9:8b55fb7eec71 (inactive) source$ hg update feature1 0 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ hg commit --close-branch -m "Feature1 included in default. No further work planned here" source$ hg branches default                       10:677a88f54dd3 feature2                       7:86391749b3c3 source$

La ramas cerradas se pueden abrir de nuevo entrando en ellas con un "hg update" y validando el cambio con "hg commit".

Hasta ahora hemos aprendido lo básico para trabajar con Mercurial en una carpeta de código fuente local. Normalmente es difícil borrar accidentalmente un directorio oculto como ".hg", pero siempre podemos perder nuestro disco duro por un fallo hardware (o uno puede meter la pata haciendo un "rm -rf" mientras escribía este artículo). En ese caso perderíamos el repositorio. Además cuando trabajemos con un equipo necesitaremos un repositorio central en el que mezclar los avances de cualquier miembro en la rama principal. Bitbucket es la respuesta para ambas necesidades. Por eso vamos a ver cómo podemos conservar un backup de nuestro repositorio en la nube de Bitbucket.

Una vez que nos hayamos registrados en Bitbucket podemos crear un nuevo repositorio:


Se puede configurar el repositorio tanto como público como privado, podemos hacer que pueda ser usado tanto con Git como con Mercurialo incluso incluir una Wiki en el repositorio de la página web. Si nuestro equipo es de menos de cinco personas, Bitbucket nos ofrecerá sus servicios de manera gratuita.

Cuando se crea un repositorio podemos subir a él nuestra copia del código fuente con el comando "hg push":

source$ hg push https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: searching for changes remote: adding changesets remote: adding manifests remote: adding file changes remote: added 12 changesets with 7 changes to 5 files (+1 heads) source$

Con el repositorio subido a Bitbucket, todos los miembros pueden conseguir una copia local del proyecto con "hg clone":
source2$ ls source2$ hg clone https://dante@bitbucket.org/dante/sourcecode . http authorization required realm: Bitbucket.org HTTP user: borjalopezm password: requesting all changes adding changesets adding manifests adding file changes added 12 changesets with 7 changes to 5 files (+1 heads) updating to branch default 4 files updated, 0 files merged, 0 files removed, 0 files unresolved source2$ ls code_1.txt  code_2.txt  code_3.txt  code_feature1.txt source2$ hg update 0 files updated, 0 files merged, 0 files removed, 0 files unresolved source2$

Hay que fijarse en el punto (".") que hay justo después de la url del "hg clone", si no lo usamos los ficheros se descargarán en una carpeta denominada "sourcecode" dentro de "source2". Suele ser una buena idea hacer un "hg update" para asegurarse de que estamos trabajando en la versión más actualizada del proyecto.

Tras eso, el miembro del equipo ya podrá trabajar en su repositorio local. Para subir los avances a Bitbucket habría que hacer un "hg push" como hicimos antes para subir los ficheros por primera vez a Bitbucket:
source2$ ls code_1.txt  code_2.txt  code_3.txt  code_feature1.txt source2$ touch code_4.txt source2$ hg add code_4.txt source2$ hg commit -m "Code_4.txt added" source2$ hg push https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user:dante password: searching for changes remote: adding changesets remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files source2$

Tras el clone inicial, los otros miembros del equipo pueden conseguir actualizaciones (como code_4.txt) con un "hg pull":

source$ hg pull https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: pulling from https://dante@bitbucket.org/dante/sourcecode searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (run 'hg update' to get a working copy) source$ hg update default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt  code_3.txt  code_4.txt  code_feature1.txt source$

¿Pero qué pasa si dos miembros hacen la modificación sobre el mismo fichero?. Supongamos que un miembro hace:
source$ ls code_1.txt  code_2.txt  code_3.txt  code_4.txt  code_feature1.txt source$ cat code_1.txt Hello World source$ echo "Hello WWW" > code_1.txt source$ hg commit -m "One line hello WWW" source$ cat code_1.txt Hello WWW source$ hg push https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: searching for changes remote: adding changesets remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files source$

Y justo un poco después otro miembro hace en su propio repositorio:
source2$ ls code_1.txt  code_2.txt  code_3.txt  code_4.txt  code_feature1.txt source2$ cat code_1.txt Hello World source2$ echo "Wide Web" >> code_1.txt source2$ cat code_1.txt Hello World Wide Web source2$ hg commit -m "Code_1 added Wide Web" source2$ hg push https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: searching for changes abort: push creates new remote head e716387febe4! (you should pull and merge or use push -f to force) source2$

Lo que ha pasado es que Bitbucket ha detectado que el segundo push contenía una versión conflictiva del fichero code_1.txt. Cuando tenemos dos versiones de un fichero en la misma rama y nivel de versión estamos ante lo que la terminología del control de versiones denomina "dos cabezas" ("two heads"). Por defecto, Bitbucket no permite que tengamos dos cabeza y recomienda que nos bajemos las últimas actualizaciones con "hg pull" y mezclarlas con nuestra versión local con un "hg  merge":
source2$ hg heads changeset:   13:e716387febe4 tag:         tip user:        dante date:        Mon Jan 20 21:46:00 2014 +0100 summary:     Code_1 added Wide Web changeset:   7:86391749b3c3 branch:      feature2 user:        dante date:        Sat Jan 18 23:55:04 2014 +0100 summary:     code_feature2.txt created source2$ hg branch default source2$

En este punto se puede ver que tenemos una cabeza por cada rama. Esta es una situación normal, pero si actualizamos:
source2$ hg pull https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: pulling from https://dante@bitbucket.org/dante/sourcecode searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) source2$

Fijémonos en el último mensaje que nos alerta de que la última actualización ha creado múltiples cabezas. De hecho, si ejecutamos "hg heads":
source2$ hg heads changeset:   14:c3a688edd25a tag:         tip parent:      12:53443797a7da user:        dante date:        Mon Jan 20 21:46:25 2014 +0100 summary:     One line hello WWW changeset:   13:e716387febe4 user:        dante date:        Mon Jan 20 21:46:00 2014 +0100 summary:     Code_1 added Wide Web changeset:   7:86391749b3c3 branch:      feature2 user:        dante date:        Sat Jan 18 23:55:04 2014 +0100 summary:     code_feature2.txt created source2$

Podemos ver que tenemos dos cabezas en la rama principal. Por eso es el momento de mezclarlas con un "hg merge":
source2$ hg merge merging code_1.txt 3 archivos que editar 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) source2$ cat code_1.txt Hello WWW World Wide Web source2$ hg commit -m "Code_1 merged with repository" source2$ hg heads changeset:   15:fed327662238 tag:         tip parent:      13:e716387febe4 parent:      14:c3a688edd25a user:        dante date:        Mon Jan 20 22:06:39 2014 +0100 summary:     Code_1 merged with repository changeset:   7:86391749b3c3 branch:      feature2 user:        dante date:        Sat Jan 18 23:55:04 2014 +0100 summary:     code_feature2.txt created source2$ hg https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: searching for changes remote: adding changesets remote: adding manifests remote: adding file changes remote: added 2 changesets with 2 changes to 1 files source2$

En caso de conflictos como este, "hg merge" abre un editor con paneles (que no muestro aquí) con el que comparar las versiones del mismo fichero que entran en conflicto entre si y modificarlas de manera que la copia local sea compatible con la de Bitbucket. Por lo general prefiero usar Mercurial desde la consola en vez de utilizar las múltiples aplicaciones gráficas existentes (como TortoiseHG), pero tengo que admitir que el editor de consola que utiliza Mercurial es un poco árido al estar basado en Vim (soy de los que prefiere Nano frente a Vim).

Una vez mezclado y validado, podemos ver que el número total de cabezas se ha reducido de nuevo a dos (una por rama) por lo que esta vez un push a Bitbucket funcionará como la seda.

Con todas estas herramientas, un equipo de desarrolladores puede trabajar simultaneamente sin pisarse los unos a los otros. Pero Bitbucket ofrece formas para que podemos contribuir con un projecto incluso si no formamos parte de su equipo de desarrollo y no tenemos acceso de escritura a su repositorio. Nos referimos a lo que se denomina forking.



Cuando hacemos fork del repositorio de otro usuario lo que ocurre en segundo plano es que el repositorio se clona en nuestra cuenta de Bitbucket. De esa manera tendremos la oportunidad de escribir y probar modificaciones contra nuestro propio repositorio. Una vez que nuestro código está preparado, podemos solicitar un "pull request" al autor original. Si él lo acepta, se realizará la mezcla entre los dos repositorios y los cambios se incorporarán al repositorio original.



OK, con esto finalizamos el artículo. Ahora estamos en condiciones de dominar las bases del control de versiones con Mercurial y Bitbucket. Siento la extensión del artículo pero quería cubrir todos los temas habituales que se puede encontrar habitualmente un proyecto independiente. Mercurial y Bitbucket tienen otras muchas opciones y refinamientos pero normalmente sólo nos los encontraremos en proyectos más complejos.

Por último, no quiero acabar este artículo sin mencionar que la mayor parte de los conceptos de este artículo son similares a los que usan Git y Github. Recomiendo visitar este tutorial introductorio a Git en el que se puede ver lo similar que es a Merccurial.

15 enero 2014

GNS3 busca apoyo para desarrollar su próxima versión

GNS3, el entorno de virtualización de laboratorios open source desarrollado en Python, acerca del cual escribí en uno de mis artículos anteriores, está buscando apoyo financiero para desarrollar su próxima versión. Su campaña en Crowdhoster ha sido un gran éxito. Con un objetivo inicial de 35.000$, han alcanzado hasta ahora 280.000$.

Las nuevas funcionalidades son muy interesantes, pero sobre todo se encuentra la inclusión de las capacidades de conmutación que nos permitirán librarnos de los trucos con los Cisco 3540 y sus tarjetas NM-16ESW para simular entornos conmutados. Otras funcionalidades nuevas son los laboratorios de seguridad, el procesado bajo demanda en la nube, etc. Hay que resaltar las opciones para empresas. Aquellas en el negocio de las redes de datos se pueden beneficiar enormemente de las posibilidades para la formación de GNS3.



Aunque la campaña inicial acabó en verano, la dejaron abierta para dejar que contribuyera la gente que no pudo hacerlo durante la campaña principal de crowdfunding.

Los paquetes que se pueden conseguir contribuyendo a la campaña van desde el acceso temprano a la versión 1.0 de GNS3, un año antes de que esta se haga pública, a paquetes premium que incluyen funcionalidades avanzadas como la suite de seguridad, el escalado bajo demanda, etc, y 2 años de acceso gratuito a los cursos de formación de GNS3. Dado que los paquetes premium son de apenas 100$ creo que realmente merecen el precio que valen.

13 enero 2014

Mercurial vs Git

Hace no mucho me encontré desarrollando una aplicación a la que no dejaba de añadirle nuevas funcionalidades y cambios. En aquel entonces no usaba ninguna herramienta de control de versiones por lo que recurría a hacer copias de seguridad en un directorio aparte. Al final el número de copias de seguridad era tan grande que no resultaba operativo. Era difícil saber lo que había hecho en cada versión por lo que la utilidad de aquel método se veía reducida a utilizar la última copia de seguridad en caso de desastre. Me di cuenta de que era el momento de aprender a utilizar una herramienta de control de versiones. Era una idea que había tenido rondando mi cabeza desde hacía tiempo pero que había ido descartando por pereza diciéndome que no merecía la pena para el tamaño y la complejidad de mis proyectos personales. Sin embargo, decidí que era el momento de animarme.

Empecé examinando las opciones existentes. No quería casarme con ninguna opción sino decidir cual era más apropiada para aprender a usar una herramienta de control de versiones, sin perjuicio de utilizar otras en el futuro si surge la necesidad.

Aunque en entornos corporativos he visto que se utiliza Subversión, opté por investigar otras opciones más populares entre desarrolladores independientes. Launchpad, la infraestructura montada por Canonical para albergar proyectos de software libre usa Bazaar, pero leí críticas negativas respecto a él sobre que se estaba quedando anticuado y en general me pareció demasiado ligado a proyectos enfocados a Ubuntu. Como mis proyectos no necesariamente se enfocan a Ubuntu decidí descartar, por ahora Bazaar. Las dos opciones siguientes eran Mercurial y Git.

Elegir entre Mercurial y Git no es fácil. Internet está llena de debates en los que se discute cual de los dos es mejor. La verdad es que hay tantos argumentos a favor tanto de uno como de otro que la conclusión final es que los dos son herramientas muy potentes que conviene conocer dado que según la situación nos puede convenir utilizar una u otra. En realidad su origen es muy similar, hace algún tiempo el grupo de trabajo que desarrolla el kernel de Linux decidió escribir su propia herramienta de control de versiones. Se abrieron dos vías de desarrollo, una capitaneada por el mismo Linus Torvals que desarrolló Git usando C, Bash y Perl (en el pecado llevarán la penitencia), la otra la lideraba Matt Mackall que desarrolló Mercurial usando C y Python. Al final se optó por Git en parte porque se finalizó unos días antes y en parte, dicen las malas lenguas, por ser obra de Linus.

En un blog bastante divertido encontré una analogía que aunque fue escrita en 2008 parece que sigue siendo aplicable a la actualidad: Git es como MacGyver mientras que Mercurial es como James Bond.

Antes de que a alguien le de un shock vamos a explicarlo. Git parte del enfoque de Unix de que cada tarea concreta se haga con un ejecutable particular, de manera que las tareas más complejas se realicen combinando los ejecutables individuales. Como consecuencia de ellos instalar Git supone la instalación de más de 100 programitas especializados en tareas concretas del control de versiones. Esto eleva la dificultad de aprender Git pero aumenta exponencialmente su flexibilidad permitiendo que pueda ser configurado para dar soporte a los workflows de desarrollo más complejos que se nos puedan ocurrir. Ese enfoque de combinar elementos sencillos para dar lugar a sistemas más potentes es lo que hace de Git el MacGyver de las herramientas de control de versión. Como ya hemos dicho, un proyecto que está haciendo uso activo de Git en su desarrollo es el del kernel de Linux.

Mercurial es sin embargo mucho más sencillo. Sólo instala un ejecutable el cual se usa en cada situación con unos argumentos u otros. Esta sencillez facilita enormemente su aprendizaje y de hecho, se dice que los que conocen Subversion encuentran realmente fácil pasar a Mercurial porque los comandos principales son muy parecidos. Hay que reconocer que en Mercurial todo es bastante intuitivo y limpio. Al final, el 80% del tiempo uno acaba usando siempre unos pocos comandos nada más. Frente a la flexibilidad de Git, Mercurial ofrece sencillez. Por eso, Mercurial es como James Bond, si lo utilizas en la situación correcta será capaz de solucionarla de manera espectacularmente elegante y aún te sobrará tiempo para tomarte un martini con vodka ;-). Sin embargo, esa sencillez no significa que Mercurial carezca de potencia, grandes proyectos de la comunidad libre lo utilizan, como el que desarrolla el mismo Python o varios de la fundación Mozilla. En realidad, por alguna razón la tendencia general es que los desarrolladores de Python prefieren Mercurial, quizás porque está más cerca del Zen de Python cuando dice: "Simple is better than complex"

Si uno trabaja en un proyecto donde el modelo de desarrollo es complejo porque implica a mucha gente y muchos frentes de trabajo quizás lo lógico sería elegir Git. Sin embargo, si la organización del desarrollo de nuestro proyecto es sencilla lo más seguro es que Mercurial nos permita avanzar de manera más rápida y efectiva.

Otro elemento a valorar es el soporte que se le da a cada herramienta de control de versiones a la hora de subir a la nube nuestros repositorios para facilitar el trabajo colaborativo. En el caso de Bazaar, el lugar emblemático para colgar proyectos es el mencionado Launchpad, pero ya hemos dicho que este se centra en el desarrollo para Ubuntu.

Para Git, el sitio más famoso donde colgar nuestro repositorio es GitHub, el cual cuenta con una tremenda popularidad dadas las interesantes posibilidades sociales que le han dado al portal, de manera que resulta muy fácil compartir código con otras personas. Su plan de precios cobra por repositorios privados de manera que hasta 5 repositorios privados deberemos pagar hasta 7$ al més. Sin embargo, podemos tener todos los repositorios públicos que tengamos sin límite de colaboradores (personas con acceso de escritura sobre el repositorio). Esto hace que proyectos como Django, hayan elegido GitHub como su repositorio público.

Para Mercurial, el sitio de referencia es BitBucket, a diferencia de GitHub cuenta con soporte tanto para Mercurial como para Git. Sus funcionalidades son similares a las de GitHub aunque la moda haga que este último tenga más seguidores. Sin embargo su plan de precios es diferente al de GitHub ya que  BitBucket cobra por número de colaboradores de manera que por debajo de 5 colaboradores nos permite tener todos los repositorios que queramos de manera gratuita, tanto públicos como privados. Eso lo hace especialmente interesante para desarrolladores que hagan muchos proyectos en solitario. Algunos proyectos famosos que hacen uso de BitBucket son PyPi o Sphinx (ver artículo anterior).

Por lo que he podido ver por ahí, muchos desarrolladores reconocen usar ambos portales: tienen sus desarrollos privados en BitBucket y cuando quieren hacer público uno y abrirlo a la colaboración de la comunidad recurren a GitHub.

En mi caso concreto, mis desarrollos son pequeños y privados por lo que empezaré usando Mercurial y BitBucket. Con eso podré familiarizarme con los procedimientos típicos del control de versiones. Y en el futuro ya veremos si me merece la pena aprender Git (y GitHub).




07 enero 2014

Ya se han publicado las conferencias de la PyConEs 2013

Aunque no parecen haber cambiado el mensaje que aparece al comienzo de la página advirtiendo de que subirán los vídeos de las conferencias próximamente, lo cierto es que en realidad ya lo han hecho. Si uno baja al listado de conferencias y pincha encima de cada una podrá ver un enlace al respectivo vídeo y otro al fichero PDF con las diapositivas.

El contenido de estas conferencias es muy recomendable para cualquier aficionado al lenguaje Python. En esas conferencias se trataron muchísimos temas a varios niveles, desde programación web hasta robótica por lo que son ideales para conocer nuevos campos en los que aplicar nuestros conocimientos de Python y nuevos recursos para mejorar nuestra productividad con ese lenguaje.

Hay que destacar la gran labor de la recién creada asociación de Python España, para la que la organización de esta PyCon era su primer gran evento. A juzgar por el tremendo éxito obtenido esperemos que no se queden ahí y volvamos a saber de ellos muy pronto... yo al menos ya me he apuntado en la agenda acudir a la PyConEs 2014.