Desarrollo


3
Mar 10

Todo sobre Git: Un enfoque ágil

Git es un sistema de control de versiones open source, totalmente gratuito. Fue diseñado por Linus Torvalds con el objetivo de posibilitar la gestión eficiente y veloz de cualquier tipo de proyecto de software. Actualmente es utilizado para gestionar el código fuente de algunos de los proyectos open source más importantes entre los que se destacan: Linux Kernel, Debian, Gnome, Ruby on Rails, Android, entre otros.

En INSIGNIA adoptamos Git como sistema de control de versiones para todos nuestros proyectos porque en él encontramos una herramienta con un sin fin de funcionalidades que podían ser adaptadas y utilizadas sin ningún problema a nuestro workflow. Nos fascinó la facilidad con la que se pueden crear  branches, realizar merge de cambios de un branch a otro y mantener todo bien sincronizado.

Luego de 2 años de utilizarlo -y exprimirlo- para gestionar nuestros proyectos, hemos encontrado muchos detalles de esta herramienta que pueden afectar negativamente la productividad del equipo sino se aplican algunas buenas prácticas y costumbres para mantener la casa en orden.

A continuación, presentamos el workflow que aplicamos en nuestra activadad diaria para utilizar Git desde un enfoque ágil y productivo.

Primero repasemos cómo es el workflow básico propuesto para Git. Para esto clonaremos uno de nuestros proyectos open source, el primer comando que deberemos ejecutar es:

  # git clone git://github.com/insignia/administrate_me.git

Esto nos descargará la última versión del proyecto en el directorio administrate_me.

Si queremos ver cuáles fueron los últimos cambios que se introdujeron en el proyecto, podemos ejecutar:

  # git log

Y obtendremos un listado cronológico con todos los cambios que se efectuaron en el código del proyecto.

Output Git log

Ahora si modificamos un archivo (por ejemplo el README del proyecto), y ejecutamos:

  # git status

Podremos ver qué archivos se encuentran modificados. El resultado sería algo así:

Output Git status

El output del comando git status, nos aporta un dato muy importante: el branch donde estamos trabajando. Como mínimo, siempre tendremos el branch master.

Para agregar nuestros cambios, debemos hacer un commit de los mismos, eso se logra ejecutando:

  # git commit -a -m "Modifico el archivo README"

Finalmente, debemos subir esos cambios al repo central (conocido técnicamente como origin), pero antes tendremos que revisar que nuestro código se encuentre actualizado con este repo, para esto:

  # git pull
  # git push

Y este sería el workflow más simple que se podría dar en Git.

La práctica más recomendada para trabajar en Git indica que todo nuestro trabajo debería ser realizado en un branch temporal y cuando estos cambios están listos, hacer un merge de esos cambios en master y su posterior push.

Para crear un branch, simplemente ejecutamos los siguientes comandos:

  # git branch mi-nuevo-branch
  # git checkout mi-nuevo-branch

O podemos ejecutar todo en único comando con:

  # git checkout -b mi-nuevo-branch

Luego, una vez que nuestros cambios están listos, los pasamos al branch master ejecutando:

  # git checkout master
  # git merge mi-nuevo-branch

O si necesitamos subir un cambio en particular, podemos pasar un commit específico de un branch a otro, ejecutando:

  # git checkout master
  # git cherry-pick 9c108bfab98afe92b03fa15edb926bc3374ce8f5

Donde 9c108bfab98afe92b03fa15edb926bc3374ce8f5 es el hash que identifica a un commit en particular.

Esta manera de utilizar Git sigue siendo simple y ofrece muchísimas bondades. El mayor beneficio es el poder crear un branch que contenga la versión estable del proyecto (nosotros normalmente nombramos a este branch como production), y en él se van agregando todos los cambios aprobados por medio de la técnica de cherry-picking.

De ser necesario, Git nos proporciona las herramientas para identificar qué commits todavía no fueron incluídos en un branch determinado (por ejemplo: en production). Para esto, tenemos que ejecutar el siguiente comando:

  # git checkout production
  # git log --reverse --cherry-pick master...

Utilizamos la bandera –reverse para saber qué commit debería ser incluído primero y con la bandera –cherry-pick master… indicamos que solamente queremos ver aquellos que se encuentren en el branch master que todavía no se encuentren en production.

Utilizando este enfoque durante un tiempo, nos encontramos con que el listado de cambios pendientes que nos retornaba Git no era del todo preciso. Al investigar las posibles causas, encontramos que Git falla al determinar los commits pendientes en aquellos casos donde se hace complicado establecer qué commit precede a otro.

Output Gitk

En este caso podemos ver que los commits no se encuentran alineados cronológicamente sino que en algunos casos, algunos commits son precedidos por dos o mas commits. Es en estos casos donde la técnica del cherry-picking nos puede arrojar resultados o comportamientos extraños.

Este es un problema que se hace demasiado notorio cuando los proyectos donde intervienen grandes equipos. De nuestra experiencia en el desarrollo de la nueva versión de Burdastyle.com (donde en el proyecto participaban equipos distribuidos entre Tucumán, Manchester y New York), encontramos que a menos que todos los miembros del equipo apliquen una política para el manejo de Git dentro del proyecto.

Luego de examinar un poco las distintas experiencias con Git de otros equipos, encontramos que el principal problema para este comportamiento incorrecto de Git era causado por la técnica utilizada para sincronizar los branches. Básicamente la técnica de pull/merge no tiene en cuenta el orden cronológico de los commits al mover commits de un branch a otro. Para solucionar esto, encontramos que también se podía sincronizar los branches por medio de la técnica fetch/rebase, de esta manera nuestros commits son pasados de un branch a otro pero teniendo en cuenta el orden cronológico de los mismos.

Utilizando esta técnica para realizar el paso de commits de un branch temporal a master, deberíamos ejecutar:

  # git checkout mi-nuevo-branch
  # git rebase master
  # git checkout master
  # git merge mi-nuevo-branch

Y si necesitaramos sincronizar nuestro branch master con origin, podríamos hacerlo de la siguiente manera:

  # git fetch
  # git rebase origin/master

De esta manera logramos preservar el orden cronológico de todos los commits.

Output Gitk

Con todo esto, el workflow ideal para trabajar con Git estaría conformado por los siguientes pasos:

  1. Realizar todo el trabajo en un brach temporal.
  2. Antes de pasar los cambios a master, ejecutar un rebase de dicho branch con master.
  3. Hacer merge de los cambios en master.
  4. Sincronizar nuestro master con origin/master utilizando fetch/rebase.
  5. Subir nuestros cambios.
  6. Si tenemos cambios que pasar a un branch estable, hacerlo con el comando cherry-pick.

Para profundizar sobre Git y sus buenas prácticas, algunos links interesantes para leer son:

El resto es práctica y mucha predisposición para solucionar de la mejor manera cualquier inconveniente con el que nos encontremos.


30
Jul 09

Howto: Configurar Rails en Ubuntu

El propósito de este post es presentar la receta oficial que utilizamos en (in)signia para encarar el setup de nuestro ambiente de trabajo.

Es muy importante resaltar que nuestro entorno de trabajo se encuentra basado en Linux (prometemos en breve dedicar un post completo a las razones/motivos que nos llevaron a inclinarnos por Linux como plataforma de trabajo). La distribución que elegimos es Ubuntu en su versión 9.04, básicamente por su popularidad y crecimiento.

El primer tema que tenemos que encarar al empezar el setup de nuestro entorno de trabajo es la instalación de Ruby. La forma tradicional de instalar esto en linux sería con el siguiente comando:

  sudo apt-get install ruby

Esto funciona pero nos acarrea un problema serio debido a que apt-get (debido a los repositorios de Ubuntu) nos instalará la versión 1.8.7 de Ruby. Versión que ha resultado ser bastante conflictiva en lo que respecta a compatibilidad con las gemas más utilizadas.

La solución a este problema es ir por el camino más seguro y compilar directamente la versión 1.8.6 de Ruby. Para ello, estos son los pasos a seguir:

  sudo apt-get -y install build-essential libssl-dev libreadline5-dev zlib1g-dev
  wget http://rubyforge.org/frs/download.php/45875/ruby-1.8.6-p287.tar.gz
  tar xzf ruby-1.8.6-p287.tar.gz && cd ruby-1.8.6-p287
  ./configure --prefix=/usr/local --with-openssl-dir=/usr --with-readline-dir=/usr --with-zlib-dir=/usr
  make
  sudo make install

Existen patch levels más nuevos (el 369 para ser exactos) pero hemos tenido serios problemas de compatibilidad con capistrano, una gema cuyo funcionamiento es indispensable.

Una vez completada la instalación de Ruby, el paso a seguir es instalar rubygems. Para esto tendremos que seguir los siguientes pasos:

  wget http://rubyforge.org/frs/download.php/60718/rubygems-1.3.5.tgz
  tar xvzf rubygems-1.3.5.tgz && cd rubygems-1.3.5
  sudo ruby setup.rb

En este punto, podemos instalar las gemas básicas para trabajar con una aplicación Rails. Para esto, simplemente corremos:

  sudo gem install rails mongrel capistrano

A continuación tenemos que seguir por la instalación de los motores de base de datos y sus repectivos adapters para Ruby. Nosotros manejamos dos motores: MySQL y SQLite3.

Empecemos por el mas liviano… Para instalar SQLite3, tenemos que seguir estos pasos:

  sudo apt-get install sqlite3 libsqlite3-dev
  sudo gem install sqlite3-ruby

Los pasos para instalar MySQL son muy parecidos (aunque es necesario instalar más paquetes):

  sudo aptitude install mysql-server mysql-client libmysql-ruby1.8 libmysqlclient-dev
  sudo gem install mysql

Hasta aquí, estaríamos en condiciones de realizar la siguiente prueba:

  rails prueba && cd prueba
  ruby script/generate scaffold Persona nombre:string apellido:string
  rake db:migrate
  ruby script/server

Luego intentamos ingresar con un browser a http://localhost:3000/personas. Debería funcionar.

Aunque en este punto nuestro entorno de trabajo posee un corazón que late bien fuerte; nos falta una pieza sumamente importante: un framework para testing. En (in)signia estamos muy familiarizados con el Behavior Driven Development, por lo que en nuestros proyectos utilizamos Rspec y Cucumber. Para instalarlos necesitamos los siguientes pasos:

  sudo apt-get install libxml2 libxml2-dev libxslt1-dev
  sudo gem install rspec rspec-rails cucumber webrat nokogiri

Las dependencias que instalamos son requeridas por la gema nokogiri.

Resta instalar un buen sistema de control de versiones, actualmente el código fuente de todos nuestros proyectos se encuentra administrado con Git. Su instalación es relativamente sencilla, basta con ejecutar:

  sudo apt-get install git-core gitk

Con esto instalamos todos los comandos de  Git, su aplicación GUI (muy util cuando las papas queman) y el autocompletion para la consola.

Sin embargo, en (in)signia procuramos trabajar con las últimas versiones disponibles del software que utilizamos diariamente. Por lo que, luego de la instalación básica, normalmente hacemos:

  wget http://kernel.org/pub/software/scm/git/git-1.6.4.tar.gz
  tar xzvf git-1.6.4.tar.gz && cd git-1.6.4
  ./configure
  make
  sudo make install

A esto le agregamos un buen .gitconfig, como este:

[alias]
  up = pull --rebase origin
  br = branch
  st = status
  ci = commit
  co = checkout
  ql = log --abbrev-commit --pretty=oneline
  qlr = log --reverse --abbrev-commit --pretty=oneline
  pending = log --reverse --abbrev-commit --pretty=oneline master --cherry-pick master...
  undo = reset --soft HEAD^
  pick = cherry-pick

[push]
	default = matching

Y mejoramos nuestro prompt, agregando las siguientes lineas en nuestro .bashrc:

function parse_git_dirty {
  [[ $(git status 2> /dev/null | tail -n1) != "nothing to commit (working directory clean)" ]] && echo "*"
}
function parse_git_branch {
  git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e "s/* \(.*\)/[\1$(parse_git_dirty)]/"
}
export PS1='\u@\h \[\033[1;33m\]\w\[\033[0m\]$(parse_git_branch)$ '

alias ack='ack-grep'

Y listo! Ya tenemos un entorno de trabajo bien sólido para encarar las exigencias de nuestro trabajo de la mejor manera.