Artículos

Combina tus commits con squash

Después de trabajar en muchos proyectos con distintos flujos de Git, he llegado a valorar la importancia de un historial de ramas limpio. Por desgracia, la forma habitual en que se usa Git no favorece ese objetivo.

A menudo, el flujo de trabajo de un desarrollador se ve así:

  1. Elegir un issue en el que trabajar
  2. Crear una nueva rama de funcionalidad
    git checkout -b my-feature
  3. Trabajar
    git commit -m "Did X"
  4. Trabajar un poco más
    git commit -m "Did Y"
  5. ¿Ya está terminado?
    git commit -m "Did Z"
  6. …
  7. Hacer push
  8. Crear una pull request

Luego, otras personas revisan la pull request y se integra en master o en otra rama. La rama de destino recibe un nuevo commit de merge acompañado de un conjunto de commits de trabajo en curso.

Nunca he encontrado utilidad en dejar todos esos commits en el repositorio. Rara vez aportan información útil o interesante sobre la funcionalidad. Generalmente no compilan correctamente ni pasan las suites de pruebas automatizadas. Además, dificultan seguir el historial del repositorio, así que ¿qué sentido tiene? Otro problema de este enfoque es que desanima a los desarrolladores a guardar en commits su código en curso por miedo a dejar commits inútiles.

La solución es combinar tus commits con squash y hacer rebase sobre tus ramas de larga duración. Al hacer squash, modificas el historial de Git y reemplazas todos tus commits de trabajo en curso por uno solo que contiene todos los cambios; luego haces rebase de ese commit sobre la rama de destino. Terminarás con un historial que contiene solo commits útiles, con buenos títulos y descripciones. Estos commits también compilarán correctamente y pasarán todas las pruebas.

Aquí tienes un ejemplo:

Tengo un repositorio Git con un archivo test.txt vacío y un commit:

commit 15e184a2ff855d833d6456cd1cd0745205b3152d (HEAD -> master)
Author: Juan Cabrera <jcabrera@sophilabs.com>
Date:   Tue Apr 21 16:22:44 2020 -0300

  Add text.txt

Creemos una nueva rama de funcionalidad llamada my-feature y agreguemos algunos commits.

commit 2d2a2e9ee5a055653c990ee1e0a32d2e85cbf45e (HEAD -> my-feature)
Author: Juan Cabrera <jcabrera@sophilabs.com>
Date:   Tue Apr 21 16:25:37 2020 -0300

  Change test.txt

commit cd11a3ddf29aaeb85fd9d4aec7ca0c27eaa9346c
Author: Juan Cabrera <jcabrera@sophilabs.com>
Date:   Tue Apr 21 16:25:27 2020 -0300

  Change test.txt

commit 15e184a2ff855d833d6456cd1cd0745205b3152d (master)
Author: Juan Cabrera <jcabrera@sophilabs.com>
Date:   Tue Apr 21 16:22:44 2020 -0300

  Add text.txt

Ahora combinemos ambos commits en uno. Debemos anotar el hash del commit inmediatamente anterior al primer commit de nuestra rama de funcionalidad. En este caso, es:

15e184a2ff855d833d6456cd1cd0745205b3152d

$ git rebase --interact
ive 15e184a2ff855d833d6456cd1cd0745205b3152d

Git abrirá tu editor de texto con un mensaje parecido a este:

pick cd11a3d Change test.txt
pick 2d2a2e9 Change test.txt

# Rebase 15e184a..2d2a2e9 onto 15e184a (2 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Dejaré el primero como pick y cambiaré el segundo a squash, guardaré el archivo y cerraré el editor.

Git abrirá una nueva ventana del editor con el nuevo mensaje del commit. Asegúrate de cambiarlo para obtener un buen título y una buena descripción, guarda y cierra.

Veamos el log otra vez:

commit 6bacb3d302b7cf25e179e67e0c32aec1711a2d7c (HEAD -> my-feature)
Author: Juan Cabrera <jcabrera@sophilabs.com>
Date:   Tue Apr 21 16:25:27 2020 -0300

  This is a great short message

  This is a great long message.

commit 15e184a2ff855d833d6456cd1cd0745205b3152d (master)
Author: Juan Cabrera <jcabrera@sophilabs.com>
Date:   Tue Apr 21 16:22:44 2020 -0300

  Add text.txt

Ahora puedes hacer push de tu rama al remoto. Quizá necesites usar --force si no es tu primer push. Luego puedes hacer un merge fast-forward de la funcionalidad a master. Si esto no es posible, tendrás que hacer rebase de la funcionalidad sobre master.

Todos los proveedores conocidos de Git (por ejemplo, GitLab, Bitbucket y Github) permiten exigir este comportamiento en las PR de todas las ramas (o de un subconjunto que elijas), lo que simplifica mucho el proceso (consulta el manual de tu proveedor para saber cómo activar esta función). Solo tienes que crear una nueva rama de funcionalidad, hacer todos los commits que quieras, hacer push, crear una pull request e integrarla. El proveedor combinará todos tus commits en uno, usará el mensaje que proporcionaste y hará un merge fast-forward sobre la rama de destino.

Tu historial de Git será mucho más limpio y útil si sigues este flujo de trabajo. Así también se gestionan la mayoría de los grandes proyectos de código abierto.

“Combina tus commits con squash” de Juan Cabrera está bajo la licencia CC BY SA. Los ejemplos de código fuente están bajo la licencia MIT.

Foto de Viviana Rishe.

Clasificado en Investigación y aprendizaje.

Lecturas relacionadas