A5.1 Hooks, alias y configuración¶
Para quién es esto
Es material avanzado. Las sesiones son reales y los números de commit serán distintos en tu equipo. Los ejemplos parten de un repositorio con un solo commit:
mkdir proyecto && cd proyecto
git init
echo '# Proyecto' > README.md && git add README.md && git commit -m 'Crea el proyecto'
Hooks: programas que Git ejecuta por ti¶
Un hook es un script que Git ejecuta automáticamente en un momento concreto: antes de hacer un commit, al comprobar su mensaje, antes de subir... Si el script termina con error (código distinto de 0), Git cancela la operación. Así se puede impedir que se cuelen cosas que no deben.
Los hooks viven en la carpeta .git/hooks/: un archivo ejecutable cuyo nombre es el del momento. Los más útiles:
| Hook | Cuándo se ejecuta | Para qué se usa |
|---|---|---|
pre-commit |
Antes de crear el commit | Pasar el formateador, el analizador o las pruebas rápidas; rechazar TODO, claves o console.log |
commit-msg |
Tras escribir el mensaje, antes de aceptar el commit | Exigir un formato de mensaje |
pre-push |
Antes de subir | Pasar las pruebas completas |
post-merge |
Tras una fusión | Reinstalar dependencias si cambiaron |
pre-commit: rechazar un TODO¶
Este hook mira lo que se va a confirmar (git diff --cached) y, si encuentra la palabra TODO en una línea nueva, cancela el commit:
$ cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
# Impide hacer commit si algo de lo que se va a confirmar contiene la palabra TODO
if git diff --cached | grep -q '^+.*TODO'; then
echo "Commit rechazado: hay un TODO sin resolver."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
$ echo 'TODO: arreglar esto' > notas.txt && git add notas.txt && git commit -m 'Añade notas'
Commit rechazado: hay un TODO sin resolver.
$ echo 'Notas del proyecto' > notas.txt && git add notas.txt && git commit -m 'Añade notas'
[main c9967fa] Añade notas
1 file changed, 1 insertion(+)
create mode 100644 notas.txt
$ git log --oneline
c9967fa Añade notas
398526b Crea el proyecto
El primer commit se rechaza con el mensaje del hook y no se crea; al quitar el TODO, el hook no protesta y el commit se hace. Si en algún caso de verdad quieres saltarte el hook, existe git commit --no-verify, pero no debería ser lo habitual:
$ cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
# Impide hacer commit si algo de lo que se va a confirmar contiene la palabra TODO
if git diff --cached | grep -q '^+.*TODO'; then
echo "Commit rechazado: hay un TODO sin resolver."
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
$ echo 'TODO: arreglar esto' > notas.txt && git add notas.txt && git commit -q --no-verify -m 'Añade notas con un TODO'
$ git log --oneline
a6484fd Añade notas con un TODO
398526b Crea el proyecto
commit-msg: exigir un formato de mensaje¶
Este hook recibe en $1 la ruta del archivo con el mensaje y comprueba, con una expresión regular, que empieza por un tipo (feat:, fix:...), es decir, que sigue los Conventional Commits de la unidad 4:
$ cat > .git/hooks/commit-msg <<'EOF'
#!/bin/sh
# El mensaje debe empezar por un tipo: feat, fix, docs, refactor, test o chore
if ! grep -qE '^(feat|fix|docs|refactor|test|chore)(\(.+\))?: .+' "$1"; then
echo "Mensaje no válido. Usa tipo: descripción (feat, fix, docs, refactor, test, chore)."
exit 1
fi
EOF
chmod +x .git/hooks/commit-msg
$ git commit --allow-empty -m 'cambios'
Mensaje no válido. Usa tipo: descripción (feat, fix, docs, refactor, test, chore).
$ git commit --allow-empty -m 'feat: añade la lista'
[main 9a4c7f1] feat: añade la lista
$ git log --format=%s
feat: añade la lista
Crea el proyecto
Un mensaje como cambios se rechaza con una explicación y uno con formato pasa.
Los hooks no viajan con el repositorio¶
Una limitación importante: la carpeta .git/ no se sube con push ni se copia con clone. Tus hooks solo existen en tu equipo: cada persona del equipo tendría que instalar los suyos. La solución es guardarlos dentro del repositorio (por ejemplo en .githooks/) y decirle a Git dónde están, con core.hooksPath:
$ mkdir .githooks && cat > .githooks/pre-commit <<'EOF'
#!/bin/sh
# Un hook compartido: está dentro del repositorio, así que viaja con él
echo "Comprobando antes de confirmar..."
EOF
chmod +x .githooks/pre-commit
$ git config core.hooksPath .githooks
$ git config --get core.hooksPath
.githooks
$ echo nuevo > nuevo.txt && git add nuevo.txt && git commit -m 'Añade un archivo'
[main 01f5d27] Añade un archivo
1 file changed, 1 insertion(+)
create mode 100644 nuevo.txt
Comprobando antes de confirmar...
El hook de .githooks/ se ha ejecutado (Comprobando antes de confirmar...). Como esa carpeta va en el repositorio, cada persona solo tiene que ejecutar una vez git config core.hooksPath .githooks tras clonar. Para algo más serio hay herramientas que lo gestionan todo (pre-commit, Husky, Lefthook).
Los hooks son una ayuda, no una garantía
Cualquiera puede saltárselos (--no-verify) o no tenerlos instalados. Lo que de verdad tiene que cumplirse debe comprobarse también en el servidor (integración continua, página siguiente), que nadie puede esquivar.
Alias: abreviar lo que repites¶
Un alias da un nombre corto a un comando largo. Se guardan en la configuración de git:
$ git config alias.lg 'log --oneline --graph --decorate --all'
$ git config alias.ult 'log -1 --format=%s'
$ git lg
* 398526b (HEAD -> main) Crea el proyecto
$ git ult
Crea el proyecto
$ git config --get-regexp '^alias'
alias.lg log --oneline --graph --decorate --all
alias.ult log -1 --format=%s
Desde ese momento, git lg es el log con grafo y git ult muestra el mensaje del último commit. Algunos alias muy usados:
| Alias | Comando | Para qué |
|---|---|---|
git st |
status -sb |
Estado corto con la rama |
git lg |
log --oneline --graph --decorate --all |
Ver el árbol de ramas |
git co |
switch |
Cambiar de rama |
git undo |
reset --soft HEAD~1 |
Deshacer el último commit conservando los cambios |
git last |
log -1 HEAD --stat |
Ver el último commit |
Los tres niveles de configuración¶
Git guarda su configuración en tres sitios, de más general a más específico; el más específico gana:
| Nivel | Opción | Dónde se guarda | Afecta a |
|---|---|---|---|
| Sistema | --system |
Un archivo de la instalación de Git | Todas las personas del ordenador |
| Global | --global |
~/.gitconfig (tu carpeta personal) |
Todos tus repositorios |
| Local | --local (por defecto) |
.git/config del repositorio |
Solo ese repositorio |
Por eso user.name y user.email se ponen globales (los mismos siempre), pero puedes cambiarlos en un repositorio concreto (por ejemplo, tu correo del trabajo) con --local. Se ve de qué nivel viene cada valor con --show-scope:
$ git config --global user.name 'Ana Ejemplo'
$ git config --local user.name 'Ana (trabajo)'
$ git config --local alias.st 'status -sb'
$ git config --show-scope --get-regexp '^(user|alias)'
global user.name Ana Ejemplo
local user.name Ana (trabajo)
local alias.st status -sb
$ git config user.name
Ana (trabajo)
Hay dos valores para user.name, uno global y otro local, y gana el local: git config user.name responde Ana (trabajo).
.gitattributes: reglas por tipo de archivo¶
El archivo .gitattributes le dice a Git cómo tratar ciertos archivos. El uso más habitual es el de los saltos de línea: Windows termina cada línea con dos caracteres (CRLF) y Linux y Mac con uno (LF), y en un equipo mixto eso provoca diferencias fantasma y archivos «modificados» que no lo están. Para ver cómo ha guardado Git un archivo, git ls-files --eol (i/ = lo que hay en el índice, w/ = lo que hay en tu carpeta):
$ printf 'línea\r\n' > windows.txt
$ git add windows.txt
$ git ls-files --eol windows.txt
i/crlf w/crlf attr/ windows.txt
$ echo '*.txt text eol=lf' > .gitattributes
$ git add --renormalize windows.txt
$ git ls-files --eol windows.txt
i/lf w/crlf attr/text eol=lf windows.txt
Al principio, el archivo se guardó con CRLF (i/crlf). Con la regla *.txt text eol=lf, al normalizarlo se guarda con LF (i/lf) y se recuerda la regla (attr/text eol=lf), de forma que todo el equipo guarda lo mismo, sea cual sea su sistema.
| Regla de ejemplo | Qué hace |
|---|---|
* text=auto |
Git decide qué archivos son de texto y normaliza sus saltos de línea |
*.sh text eol=lf |
Los .sh siempre con LF (con CRLF no se ejecutan en Linux) |
*.bat text eol=crlf |
Los .bat siempre con CRLF |
*.png binary |
Son binarios: no se tocan ni se intentan mostrar como texto |
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
| Un hook sin permiso de ejecución | chmod +x .git/hooks/pre-commit: sin eso, Git lo ignora |
Un pre-commit lento |
Que solo compruebe lo rápido; lo pesado, en pre-push o en el servidor |
| Dar por hecho que el equipo tiene tus hooks | .githooks/ en el repositorio y core.hooksPath |
Poner --global cuando querías algo solo de este proyecto |
Sin opción (o --local) se guarda en el repositorio |
| Líneas «modificadas» en todo el archivo al cambiar de sistema | Un .gitattributes con * text=auto |
Para practicar¶
Los ejercicios GA5.1 y GA5.2 de GA5 · Ejercicios piden un hook commit-msg y un alias.