Entendiendo el Workflow de Git

25/06/2013

git
Si no entiendes las motivaciones que hay detrás del diseño de Git, eres candidato a vivir un mundo de penurias. Con suficiente empeño puedes forzar a Git a actuar del modo que tú crees que debería hacerlo en vez del modo en que él quiere hacerlo. Pero eso es como usar un destornillador como si fuera un martillo; hace el trabajo, pero lo hace mal, lleva más tiempo, y estropea el destornillador.

Piensa en cómo se comporta un workflow (flujo de trabajo) Git.

Creas una rama de Master (Maestro), haces tu trabajo, y cuando has finalizado haces el merge (mezcla) en el Master.

La mayoría de las veces este comportamiento es el que esperas ya que el Master se modificó desde que tú hiciste la rama. Entonces, un buen día, haces un merge de una rama en el Master, pero el Master no había sido modificado. En vez de crear un commit de merge, Git apunta el Master al último commit de tu rama, es decir, hace un “fast forwards” (avance rápido).

Fast Forward

Desafortunadamente, tu rama contiene commits intermedios (checkpoint commits), commits frecuentes que salvaguardan tu trabajo pero que dejan el código en un estado inestable. Ahora estos commits son indistinguibles de los commits estables de la Master. Fácilmente podrías hacer un roll back (vuelta atrás) a un desastre.

Así que te pones una nueva regla: “cuando realice un merge de la rama, usaré –no-ff para forzar un nuevo commit”. Esto cumple su función, así que continúas.

Entonces, un día, descubres un bug (error) crítico en producción y tienes que averiguar cuando se introdujo. Ejecutas bisect pero te quedas en los checkpoint commits. Abandonas e investigas a mano.

Acotas el bug a un solo fichero. Ejecutas blame para ver como ha cambiado en las últimas 48 horas. Sabes que es imposible, pero blame te indica que el fichero no ha sido tocado en semanas. blame informa de modificaciones desde el momento del commit inicial, no del merge. Tu primer checkpoint commit modificó este fichero hace semanas, pero el cambio fue mezclado hoy.

El tipo de soluciones no-ff, el bisect roto, y los misterios de blame son síntomas de que estás utilizando un destornillador como martillo.

Repensando el Control de Revisiones

El control de revisiones existe por dos motivos.

El primero es para ayudar en el acto de escribir código. Necesitas sincronizar cambios con tus compañeros, y hacer copias de tu trabajo regularmente. (El envío de ficheros zip no es una opción escalable.)

La segunda razón es la gestión de la configuración. Esto incluye la gestión de líneas de desarrollo paralelas, como trabajar en la próxima versión mientras se aplican correcciones ocasionales a bug a la versión existente en producción. La gestión de la configuración también se utiliza para saber exactamente cuando ha cambiado algo, una herramienta indispensable para diagnosticar bugs.

Tradicionalmente, estas dos razones entran en conflicto.

Cuando prototipas una funcionalidad, deberías hacer checkpoint commits regulares. Sin embargo, estos commits generalmente rompen la construcción.

En un mundo perfecto, cada cambio de tu historial de revisiones es preciso y estable. No existen checkpoint commits que creen “líneas de ruido”. No existen commits gigantes, de 10.000 líneas. En un historial limpio es fácil revertir cambios o hacer cherry-pick de los mismos entre ramas. Un historial limpio es fácilmente inspeccionable y analizable posteriormente. Sin embargo, mantener un historial limpio significaría esperar a hacer check in (subir) de los cambios hasta que son perfectos.

Así que, ¿que camino eliges? ¿Commits regulares, o historial limpio?

Si estás trabajando en el prelanzamiento de un startup con solo dos tíos, un historial limpio no te aporta demasiado. Puedes ir haciendo commit de todo al Master, y desplegarlo cuando sientas que te gusta.

A medida que aumentan las conesecuencias de los cambios, por ejemplo en un equipo de desarrollo grande o debido al volumen de usuarios, necesitas herramientas y técnicas que mantengan las cosas en orden. Esto incluye test automatizados, revisiones de código, y un historial limpio.

Las ramas parecen una buena opción intermedia. Solucionan los problemas básicos del desarrollo en paralelo. Piensas en la integración en el momentos menos importante, durante la escritura de código, pero esto te llevará algún tiempo.

Cuando tu proyecto tiene una escala suficientemente grande, este sencillo flujo de branck/commit/merge se desmorona. El momento de andar con celofanes ha terminado. Necesitas un historial de revisiones limpio.

Git es revolucionario porque te da lo mejor de ambos mundos. Puedes hacer check in regularmente de tus cambios a medida que prototipas una solución pero enviando un historial limpio cuando has finalizado. Cuando este es tu objetivo, lo que hace Git por defecto cobra mucho más sentido.

El Workflow

Piensa en ramas en dos categorías: púbica y privada.

Las ramas públicas son el historial autorizado de un proyecto. En una rama pública, cada commit deberías ser preciso, atómico, y tener un mensaje de commit bien documentado. Debería ser tan lineal como sea posible. Debería ser inmutable. Las ramas públicas incluyen Master y las ramas de versiones.

Una rama privada es para ti. Es el papel de sucio que usas mientras resuelves un problema.

Es mucho más seguro mantener la ramas locales privadas. Si necesitas subir alguna, quizás para sincronizar el ordenador de tu oficina y el de tu casa, diles a tu compañeros que la rama que has subido es privada y que no basen trabajo en ella.

Nunca deberías hacer un merge de una rama privada directamente en una rama pública con un merge plano. Primero, limpia tu rama con herramientas como reset, rebase, squash (apiñar) merges, y ammend (corregir) un commit.

Siéntete un escritor y haz de cada commit el capítulo de un libro. Los escritores no publican los primeros borradores. Michael Crichton dijo, “Los grandes libros no son escritos, son re-escritos”.

Si vienes de otros sistemas, modificar el historial es tabú. Estás condicionado a que cualquier cosa subida está escrita en piedra. Según esa lógica deberíamos deshabilitar el “deshacer” de nuestros editores de texto.

Los pragmáticos se preocupan de los cambios hasta que los cambios se convierten en ruido. En la gestión de la configuración, nos preocupamos de los grandes cambios. Los checkpoint commits son solamente un buffer etéreo que permite deshacer cambios.

Si usas tu historial púbico como el primario, los merges fast-forward no solo son seguros sino preferibles. Mantienen el historial de revisiones lineal y más fácil de seguir.

El único argumento que queda para –no-ff es la “documentación”. La gente puede utilizar los commits de merge para representar a la última versión de código desplegada en producción. Eso es un atripatrón. Utiliza tags (etiquetas).

Guías y ejemplos

Yo utilizo tres enfoques básicos dependiendo del tamaño de mi cambio, cuanto tiempo he estado trabajando en él, y en qué medida la rama se ha desviado.

Trabajo de corta duración

La mayor parte del tiempo, mi limpieza es un simple squash merge.

Imagina que creo una rama y que realizo una series de checkpoint commits durante la próxima hora:

git checkout -b private_feature_branch
touch file1.txt
git add file1.txt
git commit -am "WIP"

Cuando he terminado, en vez de hacer un git merge por defecto, ejecutaré:

git checkout master
git merge --squash private_feature_branch
git commit -v

Y emplearé un minuto escribiendo un mensaje detallado del commit.

Trabajo largo

A veces una característica lleva varios días de trabajo en el proyecto, con docenas de pequeños commits.

Decido entonces que mi cambio se debería dividir en pequeños cambios, de modo que squash es un instrumento demasiado tosco. (Como regla general me pregunto: “¿Sería sencillo hacer una revisión de código de esto?”).

Si mis checkpoint commits siguieron una progresión lógica, puedo utilizar el Modo Interactivo de rebase.

El modo interactivo es poderoso. Puede utilizarlo para editar commits antiguos, dividirlos, reordenarlos, y, en este caso, hacer squash con alguno.

En mi rama:

git rebase --interactive master

En este momento se abre un editor con una lista de commits. En cada línea aparece la operación a realizar, el SHA1 del commit, y el mensaje del commit. También aparece una leyenda con la lista de los posibles comandos.

Por defecto, cada commit utiliza “pick” (coger), lo cual no modifica el commit.

pick ccd6e62 Work on back button
pick 1c83feb Bug fixes
pick f9d0c33 Start work on toolbar

Modifico la operación a “squash”, que apiña el segundo commit con el primero.

pick ccd6e62 Work on back button
squash 1c83feb Bug fixes
pick f9d0c33 Start work on toolbar

Cuando guardo y ciero, un nuevo editor me pregunta por el mensaje del commit para el commit combinado, y ya estoy listo.

Quebrando la rama

Puede que mi rama de desarrollo exista durante mucho tiempo, y que tenga que hacer merge de varias ramas en ella para mantenerla al día mientras trabajo. El historial sería enrevesado. Es más sencillo recuperar diff crudo (raw) y crear una rama límpia.

git checkout master
git checkout -b cleaned_up_branch
git merge --squash private_feature_branch
git reset

Ahora tengo un directorio de trabajo con todos mis cambio y nada de la basura de la anterior rama. Ahora añado y hago commit de mis cambios manualmente.

Sumario

Si estás luchando contra el funcionamiento por defecto de Git, pregúntate porqué.

Trata el historial público como inmutable, atómico, y sencillo de seguir. Trata el historial privado como desechable y maleable.

El workflow pretendido es:

  • Crea una rama privada a partir de una rama pública.
  • Haz commit regularmente de tu trabajo a esta rama privada.
  • Una vez que tu código es perfecto, limpia su historial.
  • Haz merge de tu rama limpia en la rama pública.

vía

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: