Was ist eine .gitignore-Datei?
Eine .gitignore-Datei ist eine Textdatei, die Git mitteilt, welche Dateien aus der Versionskontrolle herausbleiben sollen. Jede Zeile ist ein Muster — *.log, node_modules/, .env — und jeder unversionierte Pfad, der auf ein Muster passt, verschwindet aus git status, wird von git add . übersprungen und niemals committet.
Das Wort unversioniert trägt fast die ganze Bedeutung dieses Satzes, und es erklärt allein mehr Fälle von „gitignore funktioniert nicht“ als alle Musterfehler zusammen. Git schaut nur für Dateien in die .gitignore, die es nicht schon verfolgt. Sobald eine Datei einmal committet wurde, verfolgt Git sie auf Dauer weiter — ganz egal, was du danach in die .gitignore schreibst.
Was der Gitignore Debugger macht
Füge einen Dateipfad und den Inhalt deiner .gitignore ein: Dieser .gitignore-Debugger liefert dasselbe Urteil wie Git — ignoriert oder nicht ignoriert — zusammen mit der genauen Regel und Zeilennummer, die dafür verantwortlich sind. Danach listet er alle Regeln auf, die auf den Pfad passen, in der Reihenfolge, in der Git sie auswertet, und markiert die, die tatsächlich entschieden hat.
Genau dieser letzte Teil fehlt bei einem einfachen Gitignore-Checker. Dass eine Datei ignoriert wird, ist selten das Schwierige. Zu wissen, welche deiner vierzig Regeln das getan hat — und warum die erwartete verloren hat — schon. Das gilt in beide Richtungen: warum Git deine Datei ignoriert, obwohl du sie committen willst, und warum es die nicht ignoriert, die du loswerden willst.
Die Engine setzt Gits Musterregeln neu um: Muster auf Basisnamen gegenüber verankerten Mustern, *, das nie über ein / hinausgeht, die drei Formen von **, die /-Suffixe nur für Verzeichnisse, Zeichenklassen, escapte Leerzeichen am Zeilenende. Ihr Verhalten ist gegen die echte Ausgabe von git check-ignore geprüft, nicht bloß angenommen.
So benutzt du ihn
- Gib den Pfad so ein, wie er in deinem Repository steht, relativ zur Wurzel —
src/config/local.json, nichtC:\projects\app\src\config\local.json. Verwende Schrägstriche; Windows-Backslashes werden für dich umgewandelt. - Füge deine komplette
.gitignoreein. Kürze sie nicht auf die eine Regel, die du in Verdacht hast — meistens ist die Rangfolge zwischen den Regeln der eigentliche Fehler, und beim Kürzen wirfst du genau diese Information weg. - Lies das Urteil und danach die Regelkette darunter. Dort hört eine Gitignore-Regel, die nicht funktioniert, auf, ein Rätsel zu sein.
Um Gitignore-Regeln gegen ein Verzeichnis statt gegen eine Datei zu testen, beende den Pfad mit einem Schrägstrich — build/. Der Unterschied ist wichtig, denn eine Regel, die auf / endet, passt nur auf Verzeichnisse.
Warum Gitignore-Regeln manchmal nicht funktionieren
Die Übeltäter, ungefähr nach Häufigkeit sortiert:
- Die Datei wird schon verfolgt.
.gitignorewirkt nicht rückwirkend. Wenn die Datei committet wurde, bevor es die Regel gab, wird die Regel vollständig übersprungen. Führegit rm --cached path/to/fileaus und committe: Die Datei bleibt auf der Platte, und Git verfolgt sie nicht mehr. - Eine spätere Regel hat sie überschrieben. Git hört nicht beim ersten passenden Muster auf.
- Ein übergeordnetes Verzeichnis ist ignoriert. Die Regel, die du für die Datei geschrieben hast, wird nie erreicht.
- Das Muster ist verankert, obwohl du es für frei gehalten hast. Ein Muster mit einem Schrägstrich an einer anderen Stelle als am Ende ist an das Verzeichnis verankert, in dem die
.gitignoreliegt:src/*.logpasst also aufsrc/debug.logund auf nichts Tieferes. Ein Muster ohne jeden Schrägstrich passt über den Basisnamen in jeder Tiefe:*.logerwischtdebug.loggenauso wiesrc/debug.logunda/b/c/debug.log. - Das Muster ist fehlerhaft. Eine nicht geschlossene Zeichenklasse wie
[abcfällt nicht auf eine wörtliche[zurück. Git bricht den Vergleich ab, und die Regel passt stillschweigend auf gar nichts mehr — nicht zu unterscheiden von einer Regel, die nie geschrieben wurde. Im Debugger erscheint sie deshalb einfach nie in der Liste der passenden Regeln.
Rangfolge der Gitignore-Regeln: die letzte Übereinstimmung gewinnt
Das ist die Sache, über die alle stolpern. Innerhalb einer .gitignore entscheidet das letzte Muster, das auf einen Pfad passt, über sein Schicksal — nicht das erste. Diese beiden Dateien enthalten identische Regeln und liefern für important.log entgegengesetzte Ergebnisse:
*.log
!important.log → important.log wird committet!important.log
*.log → important.log wird ignoriertSetze also breite Regeln zuerst und Ausnahmen danach. Über Dateien hinweg gewinnt eine .gitignore in einem Unterverzeichnis gegen eine weiter oben, und zwar für die Pfade darunter. Git liest außerdem .git/info/exclude, das nur zum Repository gehört und nie committet wird, sowie deine globale core.excludesFile. Eine Regel, die du in keiner .gitignore findest, versteckt sich oft an einer dieser beiden Stellen — git check-ignore -v zeigt die Datei und die Zeile, die entschieden haben.
Negationsregeln, und warum ! so oft scheitert
Ein ! vor einem Muster nimmt etwas wieder auf, das eine frühere Regel ignoriert hat. Das hat zwei Grenzen, und die zweite erklärt so gut wie jeden Fall von „die Gitignore-Negation funktioniert nicht“.
Erstens muss vorher irgendetwas den Pfad ignoriert haben. !important.log allein bewirkt nichts, weil es nie etwas zu überschreiben gab.
Zweitens, und weit überraschender: Du kannst eine Datei in einem ignorierten Verzeichnis nicht wieder aufnehmen. Das hier funktioniert nicht, und kein Umsortieren hilft:
build/
!build/keep.txtWenn build/ ignoriert ist, steigt Git nie hinein. Es sieht sich build/keep.txt nicht an und kommt deshalb nie bei Zeile 2 an. Ignoriere stattdessen den Inhalt des Verzeichnisses — dann bleibt das Verzeichnis selbst durchlaufbar:
build/*
!build/keep.txtUm etwas Tieferes zu retten, muss auch jedes Zwischenverzeichnis erreichbar bleiben — build/*, dann !build/assets/, dann !build/assets/logo.svg.
Ignorierte übergeordnete Verzeichnisse
Gits Verzeichnisdurchlauf stoppt beim ersten ignorierten Verzeichnis, auf das er trifft, und alles darunter erbt das Urteil, ohne geprüft zu werden. Darum gewinnt ein ignoriertes übergeordnetes Verzeichnis gegen jede Regel, die du für die Datei selbst geschrieben hast, so genau sie auch aussieht.
In diesem Fall nennt der Debugger das übergeordnete Verzeichnis, das den Durchlauf blockiert hat, statt nur zu sagen, dass die Datei ignoriert wird — und er zeigt trotzdem die Negationsregeln, die du für die Datei geschrieben hast, markiert als unerreichbar, weil Git nie weit genug gekommen ist, um sie zu berücksichtigen. Korrigiere zuerst das Verzeichnis, dann funktionieren die Regeln darunter von selbst wieder.
Häufige .gitignore-Fehler
- Die Regel erst nach dem Commit der Datei hinzufügen. Die Regel ist richtig und wirkungslos. Siehe
git rm --cachedweiter oben. - Kommentare am Zeilenende.
#beginnt einen Kommentar nur am Anfang einer Zeile, also ist*.log # debug outputein einziges langes Muster, das auf nichts passt, was du hast. - Backslashes als Trenner. In einer
.gitignoreist\ein Escape-Zeichen und nie ein Pfadtrenner.src\configbedeutet den wörtlichen Namensrcconfig. Schreibe immersrc/config. - Erwarten, dass
*Verzeichnisse überspringt.*stoppt an einem Schrägstrich, also gehtsrc/*/test.jsnur eine Ebene tief. Nimmsrc/**/test.jsfür beliebige Tiefe. - Den Schrägstrich am Ende vergessen.
logspasst sowohl auf eine Datei namenslogsals auch auf das Verzeichnis;logs/passt nur auf das Verzeichnis. - Glauben, Ignorieren löscht die Historie. Eine Regel verhindert künftige Commits. Sie entfernt kein Geheimnis, das schon gepusht wurde — tausche die Zugangsdaten aus.
Ein paar Beispiele aus der Praxis
Der Pfad src/logs/debug.log ist gegen diese Regeln nicht ignoriert. Die Negation ist die letzte Übereinstimmung, und kein übergeordnetes Verzeichnis blockiert den Durchlauf — sie wird also erreicht und gewinnt:
*.log
!src/logs/debug.logDer Pfad build/keep.txt ist gegen diese Regeln ignoriert, wegen build/ in Zeile 1, das auf das übergeordnete Verzeichnis passt. Zeile 2 greift nie:
build/
!build/keep.txtDer Pfad docs/api/notes.md ist gegen diese Regel nicht ignoriert, weil das Muster durch seinen Schrägstrich verankert ist und * den in api/notes.md nicht überspringt. Gemeint war docs/**/*.md:
docs/*.mdFüge irgendeines dieser Beispiele oben in den Gitignore-Tester ein, um die vollständige Regelkette zu sehen. Zurück im Terminal ist git check-ignore -v path/to/file das eingebaute Gegenstück — es nennt Quelldatei, Zeilennummer und Muster, die entschieden haben, und das lohnt sich zu kennen.