Precedencia de gitignore y patrones de negación (!)
Explica por qué gana la última regla, la precedencia entre .gitignore, info/exclude y la configuración global, y por qué no se puede recuperar con ! un archivo cuyo directorio padre está excluido, con su solución.
Cuando las reglas de gitignore no funcionan como se espera, la causa casi siempre está en el orden y en el directorio padre. Este artículo resume qué regla sigue git entre varias.
Dentro del mismo archivo: gana la última regla que coincide
Dentro de un .gitignore se lee de arriba abajo y el último patrón que coincide decide el resultado. Si ese patrón empieza por !, no se ignora; si no, se ignora.
*.log
!important.log
important.log coincide con ambas líneas, pero la última es una negación, así que se rastrea. Si se invierte el orden, el resultado también se invierte.
!important.log
*.log
Ahora la última coincidencia de important.log es *.log, así que se ignora. Por eso, al combinar varias plantillas en el generador, el orden de selección es el orden de las reglas. Por ejemplo, al usar juntos el *.jar de la plantilla de Java y el !gradle-wrapper.jar de la plantilla de Gradle, Gradle debe ir después.
Precedencia entre varios archivos
git lee reglas de estos cuatro lugares. Cuanto más arriba, mayor es la precedencia.
- Patrones recibidos en la línea de comandos (algunos comandos, como
git ls-files --exclude) - El
.gitignoredel mismo directorio que la ruta o de un directorio padre — el archivo más profundo sobrescribe al superior $GIT_DIR/info/exclude(normalmente.git/info/exclude)- El archivo al que apunta el ajuste
core.excludesFile(gitignore global)
Por ejemplo, si el .gitignore de la raíz tiene *.tmp y sub/.gitignore tiene !*.tmp, sub/y.tmp se rastrea según la regla de negación del archivo inferior. En cambio, aunque escribas !x en .git/info/exclude, la x de .gitignore tiene prioridad, así que x sigue ignorado.
$ git check-ignore -v x y.tmp sub/y.tmp
.gitignore:1:x x
.gitignore:2:*.tmp y.tmp
sub/.gitignore:1:!*.tmp sub/y.tmp
La excepción más importante: si el directorio padre está excluido, no se puede recuperar
La documentación de git lo dice así: si el directorio padre de un archivo está excluido, ese archivo no se puede volver a incluir. Por rendimiento, git no mira en absoluto dentro de los directorios excluidos, así que las reglas ! sobre archivos de su interior ni siquiera se leen.
logs/
!logs/keep.txt
Con estas reglas, logs/keep.txt sigue ignorado. Si ejecutas git check-ignore -v, la regla decisiva que aparece es logs/.
La solución es excluir el contenido del directorio y no el directorio.
logs/*
!logs/keep.txt
logs/* no coincide con el propio directorio logs, así que git entra en él y aplica la regla de negación a keep.txt. El /log/* + !/log/.keep de la plantilla de Rails y el .vscode/* + !.vscode/settings.json de la plantilla de VS Code usan este mismo principio.
Para recuperar rutas más profundas hay que recuperar también, uno a uno, los directorios intermedios.
config/*
!config/app/
config/app/*
!config/app/defaults.yml
Patrón de lista de permitidos (allowlist)
El mismo principio sirve cuando, en lugar de enumerar lo que se ignora, quieres enumerar solo lo que se rastrea.
# Ignorar todo lo de la raíz
/*
# Volver a incluir solo lo necesario
!/.gitignore
!/src/
!/README.md
/* solo coincide con cada elemento de la raíz y no excluye la raíz misma, por eso funciona !/src/. Con este método, el propio .gitignore también cae en /*, así que no hay que olvidar !/.gitignore. De hecho, si ejecutas git status --ignored sin esta línea, .gitignore aparece en la lista de ignorados.
Reglas duplicadas y orden
Si la misma regla aparece varias veces, la anterior no influye en el resultado, porque la misma regla posterior siempre coincide más tarde. En cambio, borrar el duplicado posterior puede cambiar el resultado si hay una regla de negación en medio.
*.log
!debug.log
*.log
Aquí, si se borra el último *.log, debug.log pasa a estar rastreado. El generador de este sitio detecta estos casos y elimina el duplicado posterior solo cuando no hay ninguna regla de negación en medio; en los demás casos lo conserva.
Referencias
- Documentación oficial de gitignore: PATTERN FORMAT, NOTES
- Documentación oficial de git-check-ignore
- Fecha de verificación: 2026-09-23 (ejemplos comprobados con git 2.50.1)