Git proporciona un mecanismo por el cual podemos ejecutar automáticamente programas o scripts que pueden realizar ciertos trabajos tras ejecutar el comando git. Se conocen como enganches (hooks) y se suelen utilizar para garantizar que ciertas condiciones se dan en el entorno local antes de publicar los cambios en el entorno remoto. Gracias a ellos, podemos detectar algunos problemas antes de que lo haga el flujo de integración continua.

Al finalizar, sabrá:

  • Qué es un enganche o hook.

  • Dónde se registran.

  • Cómo evitar su ejecución.

Introducción

Un enganche de Git (Git hook) es un mecanismo que ejecuta alguna operación automáticamente antes o después de una operación de git. El comando git genera ciertos eventos a los que podemos asociar un hook que ejecute algo cuando se invoquen. Estos enganches son muy útiles para ejecutar comandos o aplicaciones que realicen alguna tarea como, por ejemplo:

  • Garantizar el formato de los mensajes de confirmación.

  • Garantizar el análisis estático de código automáticamente.

  • Forzar las precondiciones para realizar una combinación de ramas.

  • Enviar notificaciones a chat rooms como Slack o Teams.

Registro de enganches

Los enganches se definen en el directorio .git/hooks del proyecto. Son programas o scripts cuyo nombre debe ser el nombre del evento al que están asociados. Por ejemplo, si deseamos definir un hook para el evento pre-commit, definiremos el script o programa en el archivo .git/hooks/pre-commit. No hay que olvidar que el hook tenga permiso de ejecución.

Pero ojo, los cambios producidos en el contenido del directorio .git no se registran. Esto quiere decir que cualquier enganche que defina no será compartido por el equipo. Por suerte, desde la versión 2.9 de Git, podemos configurar un directorio específico que no sea .git/hooks, lo que nos permitirá hacer seguimiento a sus cambios. Esto se consigue mediante la opción de configuración core.hooksPath:

git config --local core.hooksPath '.githooks'

Opción --no-verify

Es posible indicarle a git que no emita los eventos relacionados con una confirmación si a esa confirmación le pasamos la opción --no-verify.

Eventos de Git

Atendiendo a quién ejecuta el enganche, distinguimos entre enganches locales y servidores. Un enganche local (client-side hook) es aquel que se ejecuta en nuestro entorno local. Mientras que un enganche servidor (server-side hook) es el que se ejecuta en el repositorio remoto como, por ejemplo, en GitHub.

En nuestro caso, vamos a presentar algunos eventos relacionados con enganches locales, es decir, que se emiten en nuestro entorno y, por lo tanto, se ejecutan localmente.

Eventos locales pre-commit y pre-merge-commit

git emite el evento pre-commit cada vez que ejecutamos el comando git commit. Lo hace justo antes de llevarlo a cabo y, si hemos definido un enganche, este podría echar atrás la confirmación si finaliza con un código de salida distinto de cero. Se suele utilizar para ejecutar el analizador de código estático con objeto de detectar incumplimientos de las directivas o convenios de la organización. Lo que nos obligará a resolver el problema, evitando así subir un cambio que incumpla estas reglas.

Ojo, esto no evita que el flujo de CI haga también esa tarea. El objeto es evitar que subamos cosas que de antemano sepamos que puedan conducir al fallo del flujo de integración continua.

Por otra parte, el evento pre-merge-commit es similar a pre-commit, salvo que se emite cuando estamos ante una confirmación realizada durante una combinación de ramas.

A modo de ejemplo, vamos a ver un hook pre-commit que ejecuta el comando go fmt para formatear el código Go, de tal manera que, si modifica algún archivo, cancele la confirmación de cambios:

#!/usr/bin/env bash

files=$(go fmt .)

if [ "$files" != '' ]; then
  printf "Los archivos siguientes se han modificado: ${files/$'\n'/, }\n"
  exit 1
fi

Evento local commit-msg

El evento commit-msg se emite después de pre-commit y antes de registrar la confirmación de cambios. Se utiliza básicamente para comprobar si el mensaje de confirmación indicado cumple con nuestros convenios. A continuación, presentamos un hook que confirma que todo mensaje de confirmación sigue un formato similar a tipo: mensaje:

#!/usr/bin/env bash

msg=$(cat $1)

if [[ ! "$msg" =~ ^[a-zA-Z0-9\(\)\-]+:.+ ]]; then
  echo "Los mensajes de confirmación deben seguir el formato 'tipo: título'."
  exit 1
fi

El enganche recibe un argumento como entrada, la ruta a un archivo generado por git cuyo contenido es el mensaje de confirmación. Si lo deseamos, podemos cambiar su contenido a un mensaje normalizado según los estándares de nuestra organización. En caso de utilizar un script de Bash, puede utilizar la variable $1 para acceder a este parámetro.

Evento local post-commit

Después de realizada una confirmación de cambios con éxito, git emite el evento post-commit. No recibe ningún argumento, pero si necesita acceder a la información de la confirmación, puede utilizar en su hook:

git log -1 HEAD

Se utiliza principalmente para automatizar notificaciones post confirmación.

Evento local post-merge

El evento post-merge se emite después de realizarse con éxito una combinación de ramas.

Evento local pre-push

El evento pre-push se emite cuando realizamos un git push, antes de que los cambios se transfieran al servidor de Git. Se suele utilizar para evitar pushes. Recibe dos argumentos:

  • El nombre del repositorio remoto.

  • La ubicación del repositorio remoto.

Evento local post-checkout

Se emite el evento post-checkout tras realizarse un cambio de rama local. Los hooks de este tipo reciben tres argumentos:

  • El nombre de la rama previa.

  • El nombre de la nueva rama, es decir, a la que acabamos de cambiar.

  • Un flag que indica si se ha realizado un branch checkout (1) o un file checkout (0).