Was wirklich ausgeschlossen werden sollte
Eine schlecht gepflegte .gitignore führt dazu, dass generierte Dateien, Zugangsdaten und IDE-Konfigurationen versehentlich im Repository landen oder dass Teammitglieder ständig lokale Änderungen wegwerfen. Dieser Artikel erklärt globale und projektspezifische Regeln, die genaue Pattern-Syntax, das Risiko bereits getrackter Dateien und eine praxistaugliche Baseline für PHP- und Magento-Projekte.
Inhaltsverzeichnis
- 1. Warum eine durchdachte .gitignore Zeit und Nerven spart
- 2. Global vs. projektspezifische .gitignore: wo welche Regel hingehört
- 3. Pattern-Syntax: Negation, Verzeichnis-Regeln und Globstar
- 4. Reihenfolge und Präzedenz: welche Regel gewinnt
- 5. Das Risiko: bereits getrackte Dateien ignorieren
- 6. Eine sinnvolle Baseline .gitignore für PHP- und Magento-2-Projekte
- 7. Tools: git check-ignore, git status --ignored und gitignore.io
- 8. Häufige Fehler beim Einsatz von .gitignore
- 9. .gitignore-Strategien im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum eine durchdachte .gitignore Zeit und Nerven spart
Eine .gitignore-Datei ist keine Formalität, sondern eine der wenigen Konfigurationsdateien, die direkten Einfluss auf die Sauberkeit eines Repositories über Jahre hinweg hat. Ohne sinnvolle Regeln landen generierte Assets, Abhängigkeitsordner wie vendor/ oder node_modules/, Build-Artefakte und lokale IDE-Konfigurationen im Commit-Verlauf. Das bläht nicht nur die Repository-Größe unnötig auf, sondern erzeugt bei jedem git status Rauschen, das echte Änderungen verschleiert und Code-Reviews erschwert.
Der zweite, oft unterschätzte Effekt betrifft die Team-Konsistenz: Wenn jeder Entwickler seine eigenen, lokal ignorierten Dateien anders handhabt, entstehen widersprüchliche Arbeitsstände. Eine Datei, die bei einer Person ignoriert und bei einer anderen versehentlich committet wird, führt zu Merge-Konflikten, die nichts mit dem eigentlichen Code zu tun haben. Eine zentrale, versionierte .gitignore-Strategie mit klarer Trennung zwischen global, projektspezifisch und lokal löst dieses Problem strukturell, statt es Team für Team neu zu improvisieren.
2. Global vs. projektspezifische .gitignore: wo welche Regel hingehört
Git kennt drei Ebenen von Ignore-Regeln, die sich in ihrer Reichweite fundamental unterscheiden. Die globale .gitignore greift für alle Repositories eines Nutzers und gehört an einen Ort außerhalb des Projekts, typischerweise ~/.gitignore_global, aktiviert über git config --global core.excludesFile. Hier gehören ausschließlich Artefakte des lokalen Systems hinein: .DS_Store auf macOS, Thumbs.db auf Windows, sowie persönliche Editor-Ordner wie .idea/ oder .vscode/, wenn das Team keine geteilten Editor-Einstellungen versioniert.
Die projektspezifische .gitignore im Repository-Root wird mitversioniert und definiert, was für alle Teammitglieder gilt: Build-Verzeichnisse, Abhängigkeitsordner, generierte Konfigurationsdateien. Diese Regeln sind Teil der Projektarchitektur und gehören in Code-Reviews genauso geprüft wie jede andere Datei. Eine dritte, oft vergessene Ebene ist .git/info/exclude im lokalen Repository: Sie funktioniert wie eine projektspezifische .gitignore, wird aber nie committet und eignet sich für rein persönliche, nicht teamrelevante Ausnahmen, etwa ein temporäres Debug-Skript.
# Set up a global gitignore for OS- and editor-specific artifacts
git config --global core.excludesFile '~/.gitignore_global'
cat > ~/.gitignore_global << 'EOF'
# macOS
.DS_Store
.AppleDouble
# Windows
Thumbs.db
desktop.ini
# Personal editor state (not shared with the team)
.idea/
.vscode/
*.swp
EOF
# Purely local, per-clone exceptions never get committed
echo "scratch-debug.php" >> .git/info/exclude
3. Pattern-Syntax: Negation, Verzeichnis-Regeln und Globstar
Die Pattern-Syntax von .gitignore wirkt auf den ersten Blick simpel, hat aber mehrere Details, die regelmäßig zu falschen Annahmen führen. Ein Pattern ohne führenden Schrägstrich matcht in jeder Verzeichnistiefe, während /build nur das Verzeichnis build im Repository-Root erfasst. Ein abschließender Schrägstrich wie logs/ begrenzt das Pattern explizit auf Verzeichnisse und lässt gleichnamige Dateien unberührt. Ohne diesen Schrägstrich matcht das Pattern sowohl Dateien als auch Verzeichnisse mit diesem Namen.
Negation mit ! erlaubt es, eine zuvor ausgeschlossene Datei wieder einzuschließen, hat aber eine wichtige Einschränkung: Ist ein übergeordnetes Verzeichnis bereits ignoriert, kann eine Datei darin nicht mehr über Negation zurückgeholt werden, weil Git den Verzeichnisinhalt gar nicht erst durchsucht. Der Globstar ** matcht beliebig viele Verzeichnisebenen, etwa **/cache/ für jeden cache-Ordner unabhängig von der Tiefe, oder logs/**/*.log für alle .log-Dateien in beliebig tiefen Unterordnern von logs. Kommentare beginnen mit #, ein führendes Raute-Zeichen als literales Zeichen wird mit einem Backslash escaped.
# Comment lines start with a hash
# Matches "debug.log" anywhere in the tree
debug.log
# Leading slash: only the root-level "build" directory
/build
# Trailing slash: directories only, same-named files stay tracked
cache/
# Globstar: matches "cache" at any depth
**/cache/
# Globstar mid-pattern: any .log file under logs/, any depth
logs/**/*.log
# Negation: re-include one specific file...
*.log
!important.log
# ...but this does NOT work if the parent dir is already ignored:
build/
!build/keep-this.txt # never re-included, Git never scans into build/
# Escaping a literal leading hash or exclamation mark
\#not-a-comment.txt
\!not-negation.txt
4. Reihenfolge und Präzedenz: welche Regel gewinnt
Wenn mehrere .gitignore-Dateien und Regeln gleichzeitig existieren, wendet Git sie in einer festen Reihenfolge an: zuerst die globale Konfiguration, dann die .gitignore-Dateien vom Repository-Root abwärts bis in das Verzeichnis der betroffenen Datei, zuletzt .git/info/exclude. Innerhalb dieser Kette gilt: Die zuletzt gelesene, spezifischere Regel gewinnt. Eine .gitignore in einem Unterverzeichnis kann also Regeln aus der Root-.gitignore für diesen Teilbaum gezielt überschreiben oder mit ! wieder aufheben, solange kein übergeordnetes Verzeichnis komplett ausgeschlossen ist.
Diese Kaskade ist besonders in modularen PHP-Projekten mit mehreren Paketen relevant, etwa in einem Magento-Modul-Verzeichnis mit eigenem Build-Prozess. Statt alle Ausnahmen zentral in der Root-.gitignore zu pflegen, kann jedes Modul eine eigene, lokale .gitignore mit modul-spezifischen Regeln mitführen. Wichtig ist dabei, die Reihenfolge im Kopf zu behalten: Eine sehr allgemeine Regel wie *.cache in der Root-Datei lässt sich in einem Unterordner nicht einfach durch !wichtig.cache aufheben, wenn zusätzlich ein übergeordnetes Verzeichnis ignoriert ist, das den Zugriff auf diesen Unterordner blockiert.
5. Das Risiko: bereits getrackte Dateien ignorieren
Der häufigste Irrtum im Umgang mit .gitignore: Ein Pattern wird zur Datei hinzugefügt, aber die betroffene Datei bleibt trotzdem in git status und in jedem weiteren Commit sichtbar. Der Grund ist simpel und in der Git-Dokumentation klar beschrieben, wird aber trotzdem regelmäßig übersehen: .gitignore wirkt ausschließlich auf untracked files. Eine Datei, die bereits einmal mit git add und git commit ins Repository aufgenommen wurde, bleibt getrackt, unabhängig davon, welche Regeln später hinzugefügt werden.
Die korrekte Lösung ist git rm --cached, das eine Datei aus dem Index entfernt, ohne sie aus dem Arbeitsverzeichnis zu löschen. Nach diesem Schritt greift die .gitignore-Regel wie erwartet. Kritisch wird es, wenn bereits getrackte Dateien sensible Daten enthalten, etwa eine .env mit Datenbank-Zugangsdaten: git rm --cached entfernt die Datei nur aus zukünftigen Commits, die Zugangsdaten bleiben aber unwiderruflich in der bisherigen Historie sichtbar für jeden mit Repository-Zugriff. In diesem Fall reicht ein einfaches Nachträglich-Ignorieren nicht aus, sondern es braucht eine Historie-Bereinigung mit git filter-repo und anschließend zwingend eine Rotation aller betroffenen Zugangsdaten.
# A file was committed by accident, then added to .gitignore afterwards
echo "app/etc/env.php" >> .gitignore
git status
# ... app/etc/env.php still shows up as "modified" - .gitignore has no effect
# Untrack it without deleting the local working copy
git rm --cached app/etc/env.php
git commit -m "Stop tracking app/etc/env.php, now covered by .gitignore"
# Verify: the file is gone from the index but still exists on disk
ls app/etc/env.php # still present locally
git ls-files app/etc/env.php # empty output, no longer tracked
# If real secrets were ever committed, rm --cached is NOT enough:
# the credentials remain visible in every historical commit.
# Purge history and then rotate every exposed credential immediately.
git filter-repo --path app/etc/env.php --invert-paths
6. Eine sinnvolle Baseline .gitignore für PHP- und Magento-2-Projekte
Eine gute Baseline unterscheidet konsequent zwischen Quellcode, der versioniert gehört, und generierten oder umgebungsspezifischen Artefakten, die es nicht sollten. Bei Magento 2 betrifft das vor allem var/ mit Caches, Logs und Session-Daten, generated/ mit automatisch erzeugten Proxy- und Factory-Klassen, sowie pub/static/ und pub/media/, deren Inhalt durch setup:static-content:deploy beziehungsweise durch Produktdaten-Importe entsteht. Composer-Abhängigkeiten in vendor/ gehören ebenfalls ausgeschlossen, da composer.lock bereits die exakten Versionen dokumentiert und reproduzierbare Installationen garantiert.
Umgebungsspezifische Konfigurationsdateien wie app/etc/env.php mit Datenbank-Zugangsdaten und Crypt-Key dürfen niemals ins Repository, selbst wenn Staging- und Produktionswerte unterschiedlich sind. Stattdessen empfiehlt sich eine env.php.dist-Vorlage ohne echte Werte, die versioniert wird. Node-Abhängigkeiten für den Hyvä-Tailwind-Build sowie Editor- und OS-Artefakte runden die Baseline ab. Wichtig: Diese Datei sollte am Anfang jedes neuen Projekts stehen, nicht erst nachträglich ergänzt werden, wenn bereits Dutzende falsche Dateien getrackt sind.
# .gitignore - baseline for a PHP / Magento 2 project
# Composer dependencies (composer.lock guarantees reproducible installs)
/vendor/
# Node dependencies for the Hyva Tailwind build pipeline
/node_modules/
# Magento generated code, caches, logs and session data
/generated/
/var/*
!/var/.htaccess
# Deployed static content and imported/uploaded media
/pub/static/*
!/pub/static/.htaccess
/pub/media/*
!/pub/media/.htaccess
# Environment-specific configuration with real credentials
/app/etc/env.php
/app/etc/config.local.php
# Local environment overrides
.env
.env.local
# Build output of the Tailwind/Hyva theme pipeline
/app/design/frontend/**/web/tailwind/tailwind-cache.css
# Editor and OS artifacts (prefer the global gitignore for these,
# duplicated here so a fresh clone is safe without extra setup)
.idea/
.vscode/
.DS_Store
Thumbs.db
# PHP tooling caches
.phpunit.cache/
.php-cs-fixer.cache
7. Tools: git check-ignore, git status --ignored und gitignore.io
Wenn ein Pattern nicht wie erwartet greift, ist Raten der langsamste Weg zur Lösung. git check-ignore -v <pfad> zeigt exakt, welche Regel in welcher Datei und Zeile für einen bestimmten Pfad zuständig ist, inklusive der gesamten Präzedenzkette aus globaler, projektweiter und lokaler .gitignore. Das ist besonders hilfreich, wenn mehrere .gitignore-Dateien in verschachtelten Verzeichnissen existieren und unklar ist, welche davon eine Datei tatsächlich ausschließt oder eine Negation wirkungslos macht.
git status --ignored listet alle ignorierten Dateien im aktuellen Arbeitsverzeichnis auf und deckt zuverlässig auf, ob eine .gitignore-Regel versehentlich zu breit gefasst ist und wichtige Dateien mit erfasst. Für den Einstieg in ein neues Projekt liefert gitignore.io beziehungsweise die offizielle GitHub-gitignore-Sammlung geprüfte Vorlagen für gängige Sprachen und Frameworks, die sich als solider Startpunkt eignen, aber immer projektspezifisch nachgeschärft werden sollten, statt sie unverändert zu übernehmen.
# .github/workflows/gitignore-check.yml
# Fail CI if a file matching .gitignore was accidentally committed
name: gitignore-check
on: [pull_request]
jobs:
check-tracked-ignored-files:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Fail if any tracked file matches .gitignore
run: |
# -c: cached (tracked) files, -i: ignored, --exclude-standard: use .gitignore
tracked_but_ignored=$(git ls-files -ci --exclude-standard)
if [ -n "$tracked_but_ignored" ]; then
echo "These tracked files match .gitignore rules:"
echo "$tracked_but_ignored"
exit 1
fi
8. Häufige Fehler beim Einsatz von .gitignore
Der wohl häufigste Fehler ist der bereits beschriebene: Eine Datei wird ignoriert, obwohl sie längst getrackt ist, ohne git rm --cached auszuführen. Ein zweiter klassischer Fehler betrifft Groß- und Kleinschreibung: Auf case-insensitiven Dateisystemen wie macOS und Windows funktioniert ein Pattern scheinbar, während dieselbe Regel auf einem case-sensitiven Linux-CI-Server eine anders geschriebene Datei nicht erfasst. Ein dritter Fehler ist das Vertrauen darauf, dass .gitignore Sicherheit bietet: Eine ignorierte .env-Datei schützt nur vor zukünftigen Commits, nicht vor bereits vorhandenen Kopien in Backups, CI-Artefakten oder der Git-Historie selbst.
Auch die stille Fehlkonfiguration durch Leerzeichen ist verbreitet: Ein Pattern mit einem versehentlichen Leerzeichen am Zeilenende wird nicht mehr als erwartet interpretiert, da Git das Leerzeichen als Teil des Musters behandelt, sofern es nicht escaped wurde. Ebenso unterschätzt wird die Reihenfolge bei Negationen: Steht !wichtig.log vor der Regel *.log, die ihn ausschließen soll, hat die Negation keine Wirkung, weil spätere Regeln in der Datei gewinnen. Wer diese Fehlerquellen kennt, spart sich Debugging-Sessions, die sich sonst über Stunden ziehen können.
9. .gitignore-Strategien im Vergleich
Die meisten Probleme mit .gitignore lassen sich auf wenige wiederkehrende Szenarien zurückführen, die sich durch den richtigen Ansatz jeweils vollständig vermeiden lassen. Die folgende Übersicht stellt typische Fehlentscheidungen der empfohlenen Strategie gegenüber.
| Szenario | Falscher Ansatz | Empfohlene Strategie | Vorteil |
|---|---|---|---|
| node_modules/ bereits getrackt | Nur zur .gitignore hinzufügen | git rm -r --cached node_modules/ |
Datei wird tatsächlich untracked |
| .env mit echten Zugangsdaten | Nachträglich ignorieren, Historie belassen | Historie bereinigen, Zugangsdaten rotieren | Keine Alt-Credentials in der Historie |
| IDE-Konfiguration (.idea/, .vscode/) | In Projekt-.gitignore für jedes Repo neu pflegen | Globale .gitignore via core.excludesFile | Einmal konfiguriert, für alle Repos aktiv |
| Generierte Assets (var/, generated/) | Ganzes Verzeichnis committen "zur Sicherheit" | Konsequent ignorieren, per CI neu erzeugen | Kleines Repository, keine Merge-Konflikte |
| Datei in Unterordner freigeben | Negation nach dem Ausschluss des Elternordners | Elternordner nicht komplett ausschließen | Negation funktioniert wie erwartet |
Die Tabelle zeigt ein wiederkehrendes Muster: Die meisten .gitignore-Probleme entstehen nicht durch fehlende Regeln, sondern durch die falsche Reihenfolge der Schritte, insbesondere beim nachträglichen Ignorieren bereits getrackter Dateien. Wer git rm --cached als festen Bestandteil des Workflows versteht statt als Notlösung, vermeidet die meisten dieser Fallstricke von Anfang an.
Mironsoft
Git-Workflows, Repository-Hygiene und Deployment-Setup für PHP- und Magento-Teams
Repository voller unnötiger Dateien?
Wir räumen bestehende Repositories auf, entfernen versehentlich getrackte Dateien sicher aus der Historie und richten eine saubere, teamweite .gitignore-Strategie für euren PHP- und Magento-Stack ein.
Repository-Audit
Versehentlich getrackte Dateien, Secrets und Altlasten identifizieren
Historie-Bereinigung
Sichere Entfernung sensibler Daten und Rotation betroffener Zugangsdaten
Baseline-Setup
Globale und projektspezifische .gitignore für den gesamten Team-Workflow
10. Zusammenfassung
Eine durchdachte .gitignore-Strategie trennt drei Ebenen sauber: global für persönliche System- und Editor-Artefakte, projektspezifisch und versioniert für teamweite Build- und Konfigurationsausschlüsse, sowie lokal über .git/info/exclude für rein persönliche Ausnahmen. Die Pattern-Syntax mit führendem Schrägstrich, abschließendem Schrägstrich für Verzeichnisse, Globstar ** für beliebige Tiefe und Negation mit ! deckt praktisch jeden Anwendungsfall ab, solange man die Präzedenzregeln kennt und beachtet, dass ein bereits ausgeschlossenes Elternverzeichnis eine Negation wirkungslos macht.
Der wichtigste praktische Punkt bleibt: .gitignore wirkt nur auf untracked files. Bereits committete Dateien müssen explizit mit git rm --cached aus dem Index entfernt werden, und bei sensiblen Daten reicht das allein nicht aus, sondern erfordert eine echte Historie-Bereinigung plus Rotation der Zugangsdaten. Eine gute Baseline für PHP- und Magento-2-Projekte, von Anfang an eingerichtet statt nachträglich geflickt, erspart genau diese schmerzhaften Aufräumarbeiten.
.gitignore-Strategien - Das Wichtigste auf einen Blick
Drei Ebenen
Global (core.excludesFile) für persönliche Artefakte, Projekt-.gitignore für Teamregeln, .git/info/exclude für lokale Ausnahmen.
Pattern-Syntax
Führender Slash: root-relativ. Endender Slash: nur Verzeichnisse. **: beliebige Tiefe. !: Negation, greift nicht bei ignoriertem Elternordner.
Bereits getrackte Dateien
.gitignore wirkt nur auf untracked files. git rm --cached ist Pflicht, bei Secrets zusätzlich Historie-Bereinigung.
Magento-Baseline
vendor/, node_modules/, var/, generated/, pub/static/, app/etc/env.php gehören konsequent ausgeschlossen.