Qu'est-ce qu'un fichier .gitignore ?
Un fichier .gitignore est un fichier texte qui indique à Git quels fichiers laisser hors du contrôle de version. Chaque ligne est un motif — *.log, node_modules/, .env — et tout chemin non suivi correspondant à un motif disparaît de git status, est sauté par git add . et n'est jamais commité.
Le mot non suivi porte l'essentiel du sens de cette phrase, et il explique à lui seul plus de cas de gitignore qui ne fonctionne pas que tous les bugs de motif réunis. Git ne consulte .gitignore que pour les fichiers qu'il ne suit pas déjà. Dès qu'un fichier a été commité, Git continue de le suivre indéfiniment, quoi que vous ajoutiez ensuite à .gitignore.
Ce que fait le Gitignore Debugger
Collez un chemin de fichier et le contenu de votre .gitignore : ce débogueur .gitignore rend le même verdict que Git — ignoré ou non ignoré — accompagné de la règle précise et du numéro de ligne responsables. Il liste ensuite toutes les règles qui correspondent au chemin, dans l'ordre où Git les évalue, et signale celle qui a réellement tranché.
C'est cette dernière partie qu'un simple vérificateur de gitignore laisse de côté. Savoir qu'un fichier est ignoré est rarement le plus difficile. Savoir laquelle de vos quarante règles l'a fait — et pourquoi celle que vous attendiez a perdu — l'est. Cela fonctionne dans les deux sens : pourquoi Git ignore votre fichier alors que vous voulez le commiter, et pourquoi il n'ignore pas celui dont vous voulez vous débarrasser.
Le moteur réimplémente les règles de motif de Git : motifs par nom de base ou ancrés, * qui ne franchit jamais un /, les trois formes de **, les suffixes / réservés aux répertoires, les classes de caractères, les espaces finaux échappés. Son comportement est vérifié contre la sortie réelle de git check-ignore, pas supposé.
Comment l'utiliser
- Saisissez le chemin tel qu'il apparaît dans votre dépôt, relatif à la racine —
src/config/local.json, pasC:\projects\app\src\config\local.json. Utilisez des barres obliques ; les antislashs Windows sont convertis pour vous. - Collez tout votre
.gitignore. Ne le réduisez pas à la seule règle que vous suspectez — la préséance entre les règles est généralement le vrai bug, et couper le fichier jette cette information. - Lisez le verdict, puis la chaîne de règles en dessous. C'est là qu'une règle gitignore qui ne fonctionne pas cesse d'être un mystère.
Pour tester des règles gitignore sur un répertoire plutôt que sur un fichier, terminez le chemin par une barre oblique — build/. La distinction compte, car une règle terminée par / ne correspond qu'à des répertoires.
Pourquoi les règles gitignore ne fonctionnent parfois pas
À peu près dans l'ordre de fréquence des coupables :
- Le fichier est déjà suivi.
.gitignoren'est pas rétroactif. Si le fichier a été commité avant l'existence de la règle, la règle est entièrement sautée. Exécutezgit rm --cached path/to/filepuis commitez : le fichier reste sur le disque et Git cesse de le suivre. - Une règle ultérieure l'a écrasée. Git ne s'arrête pas au premier motif qui correspond.
- Un répertoire parent est ignoré. La règle que vous avez écrite pour le fichier n'est même jamais atteinte.
- Le motif est ancré alors que vous le croyiez libre. Un motif contenant une barre oblique ailleurs qu'à la fin est ancré au répertoire qui contient le
.gitignore:src/*.logcorrespond donc àsrc/debug.loget à rien de plus profond. Un motif sans aucune barre oblique correspond par nom de base à n'importe quelle profondeur :*.logattrape aussi biendebug.logquesrc/debug.logeta/b/c/debug.log. - Le motif est mal formé. Une classe de caractères non terminée comme
[abcne retombe pas sur un[littéral. Git abandonne la correspondance et la règle ne correspond silencieusement à rien du tout — indiscernable d'une règle jamais écrite. Dans le débogueur, elle n'apparaît simplement jamais dans la liste des règles correspondantes.
Préséance des règles gitignore : la dernière correspondance gagne
C'est celle qui surprend. Au sein d'un même .gitignore, c'est le dernier motif qui correspond à un chemin qui décide de son sort — pas le premier. Ces deux fichiers contiennent des règles identiques et donnent des résultats opposés pour important.log :
*.log
!important.log → important.log est versionné!important.log
*.log → important.log est ignoréPlacez donc les règles larges d'abord et les exceptions ensuite. D'un fichier à l'autre, un .gitignore situé dans un sous-répertoire l'emporte sur un autre plus haut pour les chemins situés en dessous. Git lit aussi .git/info/exclude, propre au dépôt et jamais commité, ainsi que votre core.excludesFile global. Une règle introuvable dans le moindre .gitignore se cache souvent dans l'un de ces deux endroits — git check-ignore -v affiche le fichier et la ligne qui ont tranché.
Les règles de négation, et pourquoi ! échoue souvent
Préfixer un motif par ! réinclut ce qu'une règle antérieure avait ignoré. Cela a deux limites, et la seconde explique la quasi-totalité des cas de négation gitignore qui ne fonctionne pas.
D'abord, quelque chose en amont doit avoir ignoré le chemin. !important.log tout seul ne fait rien, parce qu'il n'y a jamais rien eu à écraser.
Ensuite, et c'est bien plus surprenant : vous ne pouvez pas réinclure un fichier situé dans un répertoire ignoré. Ceci ne fonctionne pas, et aucun réordonnancement n'y changera quoi que ce soit :
build/
!build/keep.txtQuand build/ est ignoré, Git n'y descend jamais. Il n'examine pas build/keep.txt, il n'atteint donc jamais la ligne 2. Ignorez plutôt le contenu du répertoire, ce qui laisse le répertoire lui-même parcourable :
build/*
!build/keep.txtPour sauver quelque chose de plus profond, chaque répertoire intermédiaire doit lui aussi rester accessible — build/*, puis !build/assets/, puis !build/assets/logo.svg.
Répertoires parents ignorés
Le parcours de répertoires de Git s'arrête au premier répertoire ignoré qu'il rencontre, et tout ce qui se trouve en dessous hérite du verdict sans être examiné. C'est pourquoi un parent ignoré l'emporte sur n'importe quelle règle écrite pour le fichier lui-même, aussi précise qu'elle paraisse.
Dans ce cas, le débogueur nomme le parent qui a bloqué le parcours au lieu de dire simplement que le fichier est ignoré, et il affiche tout de même les règles de négation que vous avez écrites pour le fichier — marquées comme inatteignables, parce que Git n'est jamais allé assez loin pour les considérer. Corrigez d'abord le répertoire et les règles en dessous se remettent à fonctionner d'elles-mêmes.
Erreurs .gitignore courantes
- Ajouter la règle après avoir commité le fichier. La règle est correcte et sans effet. Voir
git rm --cachedplus haut. - Commentaires en fin de ligne.
#n'ouvre un commentaire qu'en début de ligne :*.log # debug outputest donc un seul long motif qui ne correspond à rien de ce que vous possédez. - Antislashs comme séparateurs. Dans un
.gitignore,\est un caractère d'échappement, jamais un séparateur de chemin.src\configdésigne le nom littéralsrcconfig. Écrivez toujourssrc/config. - Attendre que
*franchisse les répertoires.*s'arrête à une barre oblique :src/*/test.jsne descend donc que d'un niveau. Utilisezsrc/**/test.jspour n'importe quelle profondeur. - Oublier la barre oblique finale.
logscorrespond à un fichier nommélogscomme au répertoire ;logs/ne correspond qu'au répertoire. - Croire qu'ignorer efface l'historique. Une règle arrête les commits futurs. Elle ne supprime pas un secret déjà poussé — changez l'identifiant.
Quelques exemples concrets
Le chemin src/logs/debug.log face à ces règles n'est pas ignoré. La négation est la dernière correspondance et aucun répertoire parent ne bloque le parcours : elle est donc atteinte et elle gagne :
*.log
!src/logs/debug.logLe chemin build/keep.txt face à ces règles est ignoré, en raison de build/ à la ligne 1 qui correspond au répertoire parent. La ligne 2 ne se déclenche jamais :
build/
!build/keep.txtLe chemin docs/api/notes.md face à cette règle n'est pas ignoré, parce que le motif est ancré par sa barre oblique et que * ne franchira pas celle de api/notes.md. C'est docs/**/*.md qui était voulu :
docs/*.mdCollez n'importe lequel de ces exemples dans le testeur gitignore ci-dessus pour voir la chaîne de règles complète. De retour dans un terminal, git check-ignore -v path/to/file est l'équivalent intégré — il nomme le fichier source, le numéro de ligne et le motif qui ont tranché, et cela vaut la peine de le connaître.