Conociendo los hooks de Git
En la edición anterior ya vimos cómo podemos crear comandos automatizados Usando ficheros Makefile para trabajar de manera homogénea en equipo. Ahora veremos cómo garantizar unos mínimos de calidad en nuestos desarrollos mediante los hooks de Git.
Introducción
¿Qué es un hook de Git?
Git hooks son shell scripts que Git ejecuta antes o después de eventos como: commit, push, and receive.
Los hooks de Git son una funcionalidad que se instala por defecto con Git, por lo que no se necesita de descarga alguna. Además se ejecutan en entorno local.
Cómo funcionan los hooks
Todo repositorio Git tiene una carpeta .git/hooks
con un shell script por cada hook que podemos configurar.
Estos shell scripts pueden ser desarrollados en el lenguaje que se prefiera siempre y cuando puedan ser ejecutados por consola.
Para activar un determinado hook basta con sobreescribir o reemplazar un determinado hook por un shell script válido y hace que dicho script sea ejecutable.
Hooks disponibles
- applypatch-msg
- pre-applypatch
- post-applypatch
- pre-commit
- prepare-commit-msg
- commit-msg
- post-commit
- pre-rebase
- post-checkout
- post-merge
- pre-receive
- update
- post-receive
- post-update
- pre-auto-gc
- post-rewrite
- pre-push
Creando nuestros propios hooks
Para ejemplificar el proceso haremos uso de los dos hooks más relevantes:
- pre-commit, que siempre se ejecuta antes de cada commit. Aquí aplicaremos las comprobaciones relativas al estilo, sintaxis, detección de dependencias no utilizadas...
- pre-push, que siempre se ejecuta antes de hacer push al repositorio remoto. Aquí aplicaremos las comprobaciones de aplicación relativas a los tests unitarios, funcionales, etc.
De este modo garantizamos que los ficheros en el commit no tienen errores y, además, siempre comprobamos antes de cada push si los cambios no causan un impacto negativo en la lógica de nuestra aplicación, minimizando así la inclusión de errores en nuestro proyecto.
Herramientas necesarias
Para poder realizar las comprobaciones, debemos instalar algunas dependencias como herramientas de desarrollo, ya que estas herramientas no deben ser instaladas en producción por razones obvias de seguridad y rendimiento:
pre-commit
#!/bin/bash
# Run '$(chmod +x pre-commit; ln -s .git/hooks/pre-commit pre-commit)' to install
RESET='\e[0m'
RED='\e[31m'
GREEN='\e[32m'
YELLOW='\e[33m'
SERVICE_NAME='api'
#----------------------------------------------------------------------------------------------------------------------
# FUNCTIONS
#----------------------------------------------------------------------------------------------------------------------
function __displayBox() #(emoji, color, message)
{
caption=$(printf "%-72s" "${3}")
echo -e "${2}"
echo -e "┌─────────────────────────────────────────────────────────────────────────────┐"
echo -e "│ ${1} ${RESET}${caption}${2} │"
echo -e "└─────────────────────────────────────────────────────────────────────────────┘"
echo -e "${RESET}"
}
#----------------------------------------------------------------------------------------------------------------------
function __checkPhpLint() #(file)
{
task=$(printf "%-40s" "PHP Linter")
output=$(docker-compose exec ${SERVICE_NAME} php -l ${1})
RETVAL=$?
if [[ $RETVAL != 0 ]]
then
hasErrors=1
echo -e " 🔴 ${task}"
echo -e ""
echo -e "${output}"
else
echo -e " 🟢 ${task}"
fi
if [[ $hasErrors == 1 ]]
then
__displayBox 👹 ${RED} 'COMMIT IS NOT ALLOWED!'
exit 1
fi
}
#----------------------------------------------------------------------------------------------------------------------
function __checkPhpCodeBeautifierAndFixer() #(file)
{
task=$(printf "%-40s" "PHPCBF - PHP Code Beautifier and Fixer")
output=$(docker-compose exec ${SERVICE_NAME} ./vendor/bin/phpcbf --standard=PSR12 ${1})
RETVAL=$?
if [[ $RETVAL != 0 ]]
then
hasErrors=1
echo -e " 🔴 ${task}"
echo -e "${output}";
else
echo -e " 🟢 ${task}"
fi
if [[ $hasErrors == 1 ]]
then
__displayBox 👹 ${RED} 'COMMIT IS NOT ALLOWED!'
exit 1
fi
}
#----------------------------------------------------------------------------------------------------------------------
function __checkPhpStan() #(file)
{
task=$(printf "%-40s" "PHPSTAN - PHP Static Analyzer")
output=$(docker-compose exec ${SERVICE_NAME} ./vendor/bin/phpstan analyse --memory-limit=2G ${1})
RETVAL=$?
if [[ $RETVAL != 0 ]]
then
hasErrors=1
echo -e " 🔴 ${task}"
echo -e "";
echo -e "${output}";
else
echo -e " 🟢 ${task}"
fi
if [[ $hasErrors == 1 ]]
then
__displayBox 👹 ${RED} 'COMMIT IS NOT ALLOWED!'
exit 1
fi
}
#----------------------------------------------------------------------------------------------------------------------
# MAIN LOGIC
#----------------------------------------------------------------------------------------------------------------------
phpFiles=$(git diff --cached --name-only --diff-filter=ACMR HEAD | grep ".php$");
for file in $phpFiles
do
echo -e "- Checking [ ${YELLOW}$file${RESET} ]"
__checkPhpLint $file
__checkPhpCodeBeautifierAndFixer $file
if [ "$1" != "--no-verify" ]; then
__checkPhpStan $file
fi
done
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# At this point all checkpoints were successfully passed
__displayBox 🚀 ${GREEN} 'GIT COMMIT IS ALLOWED!'
exit 0
pre-push
Como ya hemos estado Usando ficheros Makefile tenemos un target test que permite ejecutar las baterías de pruebas de nuestra aplicación bajo demanda. Para simplificar el script, haremos uso de dicho comando:
#!/bin/bash
# Run '$(chmod +x pre-push; ln -s .git/hooks/pre-push pre-push)' to install
RESET='\e[0m'
RED='\e[31m'
GREEN='\e[32m'
YELLOW='\e[33m'
#----------------------------------------------------------------------------------------------------------------------
# FUNCTIONS
#----------------------------------------------------------------------------------------------------------------------
function __displayBox() #(emoji, color, message)
{
caption=$(printf "%-72s" "${3}")
echo -e "${2}"
echo -e "┌─────────────────────────────────────────────────────────────────────────────┐"
echo -e "│ ${1} ${RESET}${caption}${2} │"
echo -e "└─────────────────────────────────────────────────────────────────────────────┘"
echo -e "${RESET}"
}
#----------------------------------------------------------------------------------------------------------------------
# MAIN LOGIC
#----------------------------------------------------------------------------------------------------------------------
if ! make tests; then
__displayBox 👹 ${RED} 'GIT PUSH IS NOT ALLOWED!'
exit 1
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# At this point all checkpoints were successfully passed
__displayBox 🚀 ${GREEN} 'GIT PUSH IS ALLOWED!'
exit 0
Descarga
Puedes descargar el fichero de ejemplo de pre-commit aquí y el fichero de ejemplo de pre-push aquí.
Versión del documento
[^v1.0]: Última Modificación: 12/03/2021