Git y Rails: Herramientas de control de revisiones

Una de las herramientas más utilizadas en el arsenal de desarrollo con Ruby on Rails, son las herramientas de control de versiones. La más usada de estas en la actualidad es Subversion (SVN), aunque existen otras como CVS.

En este artículo veremos cómo estas herramientas son un componente esencial del desarrollo bajo Rails. Además presentaremos una herramienta que por su naturaleza ágil y vanguardista, se integra óptimamente tanto con Rails como con el paradigma de desarrollo distribuido y abierto. Hablamos del poderoso Git. Ahora podemos utilizar la misma herramienta que se emplea para administrar el desarrollo del kernel de Linux, en nuestros proyectos de Rails. Cálculos recientes estiman que para finales del 2008, un 80% de los proyectos y desarrolladores de Rails habrán migrado a Git. ¡Vamos a adelantarnos!

Estas herramientas nos permiten mantener un historial de cambios al proyecto, en un repositorio compartido y centralizado, crear ramas de desarrollo para realizar pruebas o cambios sin alterar nuestro código de producción, y son un vehículo para la colaboración, permitiendo a mucha gente participar en el proyecto y auxiliando al integrar sus modificaciones y cambios. En Rails son un componente esencial del desarrollo bajo las prácticas recomendadadas; de hecho, el objetivo de algunos componentes de Rails, como las migraciones, es proveer un mecanismo que nos permita conservar la historia de los cambios que se han realizado al esquema de datos, y para ello es básico contar con un repositorio donde se almacenen las revisiones del código que compone nuestro proyecto.

Adicionalmente, las herramientas de despliegue (deployment) como Capistrano o Vlad the Deployer, emplean al sistema de control de revisiones como un mecanismo para la distribución del código. Con estas herramientas, el desarrollador primero "publica" la versión del software que desea desplegar, enviándola al sistema de control de revisiones. Luego se invoca la herramienta de despliegue, que ingresa al servidor de producción, y obtiene la copia operativa del software extrayéndola del sistema de control de revisiones (lo que en terminología de control de revisiones se identifica como un "checkout"). Finalmente la herramienta de despliegue ejecuta las últimas migraciones (de aquí se ve la importancia de mantener las migraciones organizadas bajo revisión de versiones), y reinicia el servidor web para cargar la última versión de la aplicación.

La herramienta de control más popular actualmente es SVN, que es sucesora conceptual del famoso CVS. Sin embargo, sin entrar en una discusión sobre los méritos y deméritos de estas herramientas, este artículo se enfocará en uno de los nuevos sistemas de control de código distribuido: Git.

Git fue escrito por Linus Torvalds para manipular el código fuente del kernel de Linux. Ha evolucionado hasta convertirse en un sistema muy potente y fácil de usar. Sus raíces nos garantizan que tiene un alto rendimiento y eficiencia, y que se presta óptimamente a un esquema de desarrollo distribuido, colaborativo, abierto y equitativo.

Sistemas de control distribuido

En un sistema de control distribuido, a diferencia de uno centralizado como SVN o CVS, cualquier persona puede "clonar" el repositorio central, completo con todas sus ramas de trabajo e historia, y trabajar sus propios cambios en su repositorio clon, independientemente del central. Estos cambios pueden "mezclarse" con el repositorio central para integrarlos a esa base de código. Sin embargo, un sistema distribuido permite que las "mezclas" se hagan entre repositorios independientes del central; esto permite a dos desarrolladores sincronizar sus repositorios entre sí, sin involucrar al central. De hecho, gracias a las posibilidades de Git y otros sistemas, el uso de un repositorio central ha quedado relegado al papel de distribución, y muchos proyectos hacen su desarrollo de forma distribuida, permitiendo que la colaboración entre desarrolladores fluya en muchas direcciones distintas, en vez de siempre hacia un nodo central como en el esquema tradicional.

Esto también significa que los sistemas de control distribuido se prestan óptimamente al trabajo "fuera de línea" ya que mantienen una copia local del repositorio completo con toda su historia, y los cambios que se hagan fuera de línea pueden "empujarse" para integrarlos a otros repositorios remotos, sean centralizados o de otros desarrolladores.

Uso de Git

Supongamos que deseo iniciar un nuevo proyecto de Rails y administrarlo por medio de git. Lo primero sería crear la aplicación:

  1. $ rails aplicacion

Posteriormente vamos a crear un repositorio de git para darle seguimiento:

  1. $ cd aplicacion
  2. $ git init
  3. $ git add .
  4. $ git commit -m "envío de archivos iniciales"

¡Listo! Usar git de forma local es muy sencillo, de forma que no hay pretexto para no llevar el control de nuestro proyecto por medio de git.

La primera línea (git init) inicializa el repositorio, que reside completamente en un subdirectorio .git. La segunda agrega los archivos que están en el directorio; esto le indica al git que será responsable de rastrear los cambios realizados a dichos archivos, y prepara los cambios para ser enviados al repositorio. Finalmente el commit envía los cambios al repositorio, con un breve mensaje descriptivo; esto los integra a la lista de revisiones.

Cada vez que hacemos cambios relevantes y deseamos integrarlos al repositorio, hacemos:

  1. $ git add .
  2. $ git commit

Al llamar git commit sin parámetros, nos preguntará un mensaje descriptivo de los cambios que hicimos. Se recomienda incluir una descripción breve de los cambios en la primera línea, y en renglones posteriores podemos detallar más los cambios realizados.

Si como parte de nuestro trabajo eliminamos o renombramos archivos, se debe hacer con los comandos correspondientes de git; esto permite que git registre el cambio de nombre del archivo, o su eliminación:

  1. $ git rm archivo_que_no_sirve.rb
  2. $ git mv archivo_1.rb archivo_nuevo.rb
  3. $ git commit

Compartiendo nuestro trabajo

En algún momento deseamos compartir nuestro trabajo con otro usuario. En el caso más simple el usuario sería otro que tuviera una cuenta en el mismo server. Para que este usuario "clone" nuestro repositorio y tenga acceso a todo nuestro historial, versiones, y cambios, puede hacerlo con algo como esto:

  1. $ git clone /home/daniel/aplicacion/

Listo, de esta forma le aparecerá un directorio "aplicacion" que es una copia exacta de nuestro repositorio, incluyendo la historia, ramas, los archivos de trabajo y en general todo lo necesario.

Recordemos que git se presta a trabajo local tanto como en red; así como en este ejemplo se clonó un repositorio local, puede clonarse a través de ssh, http (que es una manera fácil de compartir un directorio de sólo lectura) y un demonio llamado git-daemon, que es un poco más complejo de configurar pero constituye la forma más eficiente de publicar un repositorio en red.

Si el usuario desea compartir su trabajo con nosotros, obviamente no puede hacerlo "escribiendo" sus cambios en nuestro repositorio (al que presumiblemente no tiene acceso de escritura). Pero sí puede informarnos que ha realizado algunos cambios en nuestro proyecto. Entonces nosotros podemos "jalar" estos cambios e integrarlos a nuestra copia:

  1. git pull /home/usuario/aplicacion

Desde luego, al "jalar" cambios git intenta realizar los cambios requeridos automáticamente, si no lo logra nos informará que ocurrió un conflicto que tendremos que resolver manualmente.

Ramas

En git es trivial crear una rama. Para ello simplemente hacemos:

  1. $ git branch experimental

Esto creará una rama experimental. Para obtener la lista de ramas que tenemos hacemos:

  1. $ git branch
  2. <code>
  3.  
  4. Se nos mostrará la lista de ramas, marcando con un asterisco la rama actual.
  5.  
  6. Vamos a cambiarnos a la rama experimental:
  7.  
  8. <code type=bash>
  9. $ git checkout experimental

Listo. En este momento nuestros archivos de trabajo fueron actualizados y reflejan el estado de la rama experimental. Sobre esta rama podemos realizar nuestros cambios y trabajo altamente experimental y que seguramente rompería nuestra aplicación de producción; como constituye una rama nueva, la historia de cambios es independiente de la rama maestra (llamada "master"), así que podemos hacer commit tantas veces como se requiera.

En cualquier momento podemos volver a la rama master:

#git checkout master

Actualizándose los archivos de trabajo a la rama correspondiente. Y claro, todo el trabajo y los cambios que hagamos sobre la rama maestra se mantienen en la historia independientemente de la rama experimental.

Al paso del tiempo las dos ramas (master y experimental) divergen y pueden contener código completamente distinto. En cualquier momento podemos mezclar las ramas para integrar los cambios. Por ejemplo, supongamos que en la rama maestra encontramos y corregimos un error, que seguramente también estará presente en la rama experimental. Para integrar este cambio en experimental, hacemos:

#git checkout experimental
#git merge master

Esto mezcla la rama master en la rama experimental (en la que estábamos trabajando en ese momento).

Supongamos ahora que hemos terminado la característica experimental, ya probada y estable, y deseamos integrarla en la rama maestra como parte de nuestra base de código para producción. Hacemos:

#git checkout master
#git merge experimental

que tiene el efecto similar al del ejemplo anterior. Ya que la rama experimental sirvió su propósito y los cambios están integrados en la rama maestra, podemos borrarla:

#git branch -d experimental

El uso de ramas en git es altamente recomendado, por la facilidad y velocidad con que pueden utilizarse.

Hasta el momento git no parece diferente de otros sistemas de control de revisiones. Lo que lo distingue es su naturaleza distribuida. En sistemas centralizados como CVS y SVN, existe un repositorio central al que un grupo cerrado de desarrolladores tiene acceso de escritura; todos los demás están limitados a obtener una copia de trabajo del repositorio, que en la mayoría de los casos no incluye la historia del proyecto; de modo que puedo empezar a trabajar en algún punto del proyecto, pero no tendré conocimiento ni acceso a las revisiones anteriores.

Si deseo mantener mi propia versión del proyecto, requiero configurar un repositorio SVN o CVS. Este repositorio es completamente independiente del original; si en cierto momento deseo colaborar mis cambios al proyecto original, requiero generar un parche y enviarlo al proyecto, y esperar que el parche sea aceptado; y más aún, que se integre limpiamente con la versión actual del proyecto; aunque esto puede garantizarse obteniendo una nueva copia de trabajo del proyecto y verificando que el parche funcione contra ella antes de enviarlo.

El problema viene al momento de integrar mi cambio a la línea principal del proyecto: la historia de cambios de mi trabajo se perderá, y en la historia del proyecto mi parche constituirá únicamente un cambio (commit). En un sistema centralizado el cambio se atribuye a quien realiza el commit; de forma que mi cambio aparecerá como realizado por algún miembro del equipo de desarrollo, que podría o no recordar el darme crédito en los comentarios del cambio.

Más aún, toda la integración debe realizarse en topología de "estrella", hacia el nodo central. Si Juan y Pedro obtienen dos copias del proyecto, trabajan en ellas, y luego quisieran integrar sus cambios, requieren enviarlos al proyecto central de forma independiente; esto fuerza una comunicación hacia un solo punto y dificulta la integración por parte de los contribuyentes al proyecto.

Git resuelve estos problemas permitiendo que cada desarrollador tenga una copia completa del repositorio con su historia, y cree ramas en su repositorio particular. También, como vimos, facilita compartir los cambios en casi cualquier dirección, entre sub-grupos de desarrolladores menores. Esto permite por ejemplo que un pequeño grupo trabaje en un cambio, lo integren entre ellos, y envíen el resultado final al proyecto principal.

Adicionalmente Git maneja el concepto del autor y el "committer", como dos actores diferentes, con lo cual se mantiene crédito de la autoría de cada cambio al tiempo que también se reconoce a la persona responsable de la integración.

Aunque la metodología distribuida podría fomentar el trabajo "aislado", ya que cada participante es libre de obtener su copia del repositorio y luego meterse a trabajar en una cueva durante meses, también nos da las herramientas para un trabajo más colaborativo, inclusivo y menos centralizado y autoritario. En realidad es responsabilidad del grupo de desarrollo y su estructura social el mantener esa colaboración.
Otras características de git

Desde luego git soporta etiquetas (tags), logs, culpas (blame), generación de diff y parches y todas las características esperadas de un sistema de control de revisiones.

Git con Rails

Cualquier desarrollador de Rails que utilice Capistrano o Vlad está familiarizado con su uso de SVN como herramienta para desplegar la aplicación. Afortunadamente, ambos programas ya permiten el uso de git como motor de distribución de código; es tan sencillo como cambiar una variable de configuración.

Para uso de git se recomienda contar con un repositorio centralizado, donde se empujen los cambios una vez que, dentro del grupo de desarrollo, han alcanzado un nivel de estabilidad razonable (ya que pasan todas las pruebas). Entonces puede utilizarse Capistrano o Vlad como siempre, para extraer la última versión del repositorio en red.

Más información

El manual de git puede consultarse aquí, un tutorial completo está en esta página y finalmente para aquellos que ya conocen el uso de svn, un curso rapidísimo sobre uso de git migrando desde svn aqui.

RAILS MÉXICO

User login

Encuesta

¿Qué editor de texto usas para programar Ruby y Rails?
TextMate
0%
Notepad++
0%
Vi/Vim
100%
Emacs
0%
Otro
0%
Total votes: 1