Las buenas prácticas (por ejemplo Version Control Best Practices) relacionadas con el control de versiones usando Git indican que hay que guardar (commit) los cambios de forma frecuente, conteniendo únicamente pequeños cambios.

Sin embargo, el desarrollo no se produce de una forma lineal, de principio a fin; a veces, lo que parece una buena idea que funciona al principio, más adelante es necesario cambiarla o modificarla…

Si guardamos todos los commits, la historia del repositorio quedará llena de estos cambios de dirección durante el desarrollo. Por este motivo, una de las opciones que tenemos es la de reescribir la historia del repositorio antes de, por ejemplo, hacer merge de la rama de feaure sobre la rama principal.

En este artículo vemos cómo conseguirlo usando git rebase.

El artículo que mejor lo explica, de los que he consultado, por su claridad y orden a la hora de exponer los pasos a realizar es Squash commits into one with Git, actualizado en 21/12/2020.

git rebase reescribe la historia del repositorio

Lo principal que debes tener en cuenta a la hora de usar git rebase es que se modifica la historia del repositorio. Por este motivo sólo deberías usarlo, en general, en cambios que todavía no hayas compartido con otros.

Cómo comprimir (squash) commits en Git

El comando que nos permite combinar commits es git rebase --interactive ${id-del-commit} (o git rebase -i ${id-del-commit}, en su forma corta).

El ${id-del-commit} especifica a partir de qué commit queremos reescribir la historia.

Podemos especificar el SHA del commit o una referencia relativa (como HEAD~3).

Suponiendo que la historia de nuestro repositorio es:

# Esta es la historia que refleja qué ha pasado durante el desarrollo
$ git log --oneline
ba06e7a (HEAD -> feature-1) Ok, funcionalidad Y completa!
150c964 Limpieza de código
28506fa Corrige warnings
529b7bc Corrige ésto y aquello (no podía funcionar a la primera ;) ) 
da2045b Implementación
b185d06 Prerequisitos para la funcionalidad Y
429c084 (master) Funcionalidad X (issue #111111) Documenta cómo funciona git rebase -i para comprimir commits
5488b91 Primer commit

Vemos que a partir del commit 429c084 se creó la rama feature-1, pero contiene commits con mensajes mejorables o que no aportan demasiado.

Tras el proceso de rebase nos gustaría tener una historia como:

# Esta es la historia que nos gustaría tener tras el rebase
$ git log --oneline
XXXXXXX (HEAD -> feature-1) Funcionalidad Y (issue #123456)
429c084 (master) Funcionalidad X (issue #111111)
5488b91 Primer commit

Si queremos reescribir la historia a partir del commit 429c084 lanzamos:

git rebase -i 429c084

Git abre el editor predefinido con la lista de commits involucrados en el rebase (todos los posteriores al commit indicado).

El editor que abre git rebase muestra los commits en orden inverso al del comando git log; git rebase muestra primero los commits más antiguos y después los más nuevos.

Por defecto, el identificador de cada commit va precedido de la acción a realizar (por defecto, pick); también se muestra el mensaje de commit para que sea más sencillo saber qué commit se está modificando.

Tras la lista de commits Git muestra ayuda con las opciones disponibles durante el proceso de rebase.

pick b185d06 Prerequisitos para la funcionalidad Y
pick da2045b Implementación
pick 529b7bc Corrige ésto y aquello (no podía funcionar a la primera ;) ) 
pick 28506fa Corrige warnings
pick 150c964 Limpieza de código
pick ba06e7a Ok, funcionalidad Y completa!

...

Si queremos comprimir todos los commits en uno sólo, seleccionamos el primero y marcamos el resto con squash (o s para abreviar):

pick b185d06 Prerequisitos para la funcionalidad Y
squash da2045b Implementación
squash 529b7bc Corrige ésto y aquello (no podía funcionar a la primera ;) ) 
squash 28506fa Corrige warnings
squash 150c964 Limpieza de código
squash ba06e7a Ok, funcionalidad Y completa!

git rebase procesa las acciones de arriba a abajo, por lo que antes del primer squash debe habe un pick.

Guarda el fichero y cierra el editor.

git rebase combina todos los commits marcados con squash con el commit marcado como pick.

A continuación, se abre un nuevo editor que contiene por defecto los mensajes de commit de los commits que van a combinarse en uno solo al ejecutar el rebase.

Puedes editar el mensaje de commit para ajustarlo al estilo del resto de commits del repositorio.

Al finalizar el rebase la historia del repositorio sería:

$ git log --oneline
8da3fcd (HEAD -> feature-1) Funcionalidad Y (issue #123456)
429c084 (master) Funcionalidad X (issue #111111)
5488b91 Primer commit