Die Git-Staging-Area (Index) im Detail
git
HEAD
Git · Version Control · Index · Git Internals
Die Git-Staging-Area (Index) im Detail
Wie .git/index wirklich funktioniert

"Staging Area" ist nur ein Spitzname. Dahinter steckt eine reale, binäre Datei mit dokumentiertem Format, eigener Prüfsumme und Stage-Nummern pro Eintrag. Dieser Artikel geht direkt in die Internals: Dateiformat, was git add technisch schreibt, wie git status und git diff den Index als Angelpunkt nutzen, wie man ihn mit git ls-files und git update-index direkt inspiziert und manipuliert, und warum git add -p überhaupt nur funktioniert, weil der Index vom Working Directory entkoppelt ist.

13 Min. Lesezeit .git/index · ls-files · update-index Git 2.x · CLI · Entwickleralltag

1. Was der Index wirklich ist: eine binäre Datei mit eigenem Format

Wer den Git-Index nur als Metapher für eine "Zwischenablage" versteht, hat lediglich die Hälfte der Wahrheit erfasst. Tatsächlich ist der Index eine reale, binäre Datei unter .git/index mit einem dokumentierten, versionierten Format. Der Dateikopf beginnt mit der Signatur DIRC (dircache), gefolgt von einer Versionsnummer, üblicherweise 2, 3 oder 4, und der Anzahl der Einträge. Jeder folgende Eintrag speichert Pfad, Dateimodus, Blob-Hash, Stage-Nummer sowie Stat-Metadaten wie ctime, mtime, Geräte- und Inode-Nummer. Am Dateiende steht eine SHA-1- oder SHA-256-Prüfsumme über den gesamten Inhalt, mit der Git Korruption zuverlässig erkennt.

Ab Version 3 unterstützt das Format zusätzliche Extensions, etwa den Cache-Tree, der bereits berechnete Tree-Hashes für unveränderte Verzeichnisse zwischenspeichert und so git commit erheblich beschleunigt, sowie die Resolve-Undo-Extension, die zuletzt aufgelöste Merge-Konflikte für ein mögliches erneutes git checkout -m vorhält. Wichtig für den Alltag: Der Index ist immer nach Pfadname sortiert, was binäre Suche erlaubt und erklärt, warum Befehle wie git ls-files ihre Ausgabe stets alphabetisch liefern, unabhängig davon, in welcher Reihenfolge Dateien gestaged wurden.

2. Der Index als Vorschau des nächsten Commits

Jeder Eintrag im Index ist wörtlich das, was git commit in ein Tree-Objekt verwandelt. Es gibt keine Übersetzung, keinen Zwischenschritt: Pfad, Modus und Blob-Hash aus dem Index werden direkt in die entsprechenden Tree-Einträge kopiert, hierarchisch nach Verzeichnissen aufgeteilt. Wer git commit ausführt, erzeugt also keinen Diff zum letzten Stand, sondern einen vollständigen, adressierbaren Snapshot exakt des aktuellen Index-Inhalts.

Der entscheidende Effizienzgewinn: Für Dateien, die seit dem letzten Commit nicht verändert wurden, erzeugt Git keinen neuen Blob. Der Index trägt für diese Pfade weiterhin den bereits bekannten Hash, und genau dieser Hash wird beim Bau des neuen Tree-Objekts einfach wiederverwendet. Nur Pfade mit tatsächlich geändertem Inhalt bekommen einen frischen Blob spendiert. Das macht den Index nicht nur zur Vorschau des nächsten Commits, sondern zum eigentlichen Bauplan, den git write-tree beim Committen abarbeitet, komplett unabhängig davon, wie viele Dateien im Repository insgesamt existieren.

3. git add im Detail: wie Inhalte wirklich in den Index gelangen

git add tut technisch zwei unabhängige Dinge. Erstens: Es liest den aktuellen Inhalt der Datei aus dem Working Directory, komprimiert ihn und schreibt ihn als neues Blob-Objekt nach .git/objects, adressiert über seinen SHA-Hash. Das passiert unabhängig davon, ob jemals ein Commit folgt. Zweitens: Es überschreibt den passenden Eintrag im Index mit diesem neuen Hash sowie aktuellen Stat-Daten wie Dateigröße, mtime und Inode-Nummer.

Diese Stat-Daten sind kein Beiwerk, sondern eine gezielte Optimierung: Ein späteres git status muss den Dateiinhalt nicht erneut hashen, wenn Größe und Zeitstempel im Dateisystem exakt mit den im Index gespeicherten Werten übereinstimmen. Erst bei einer Abweichung greift Git zum tatsächlichen Content-Hashing. Auf großen Magento-Repositories mit tausenden Dateien ist dieser Stat-Cache der Grund, warum git status in Sekundenbruchteilen antwortet, statt jede Datei neu einzulesen.


# git add: new blob object plus updated index entry
$ echo "<?php echo 'v2';" > app/code/Mironsoft/SeoSuite/Helper.php
$ git add app/code/Mironsoft/SeoSuite/Helper.php

$ git status --short
A  app/code/Mironsoft/SeoSuite/Helper.php

# git ls-files -s: mode, blob hash, stage, path, straight from the index
$ git ls-files -s app/code/Mironsoft/SeoSuite/Helper.php
100644 8f94139338f9404f26296befa88755fc2598c289 0	app/code/Mironsoft/SeoSuite/Helper.php

# The blob already exists in the object store, even without a commit
$ git cat-file -p 8f94139338f9404f26296befa88755fc2598c289
<?php echo 'v2';

4. git status und git diff: der Index als Angelpunkt

git status ist im Kern ein doppelter Vergleich, der beide Male den Index als Angelpunkt nutzt: einmal Working Directory gegen Index, einmal Index gegen HEAD. "Changes to be committed" listet den zweiten Vergleich, "Changes not staged for commit" den ersten. Für den ersten Vergleich reicht meist der Stat-Cache aus dem vorherigen Abschnitt, für den zweiten wird der Index-Tree tatsächlich gegen den im letzten Commit gespeicherten Tree aufgelöst.

Dieselbe Zwei-Wege-Logik gilt für git diff, nur dass der Befehl je nach Flag ein anderes Bereichspaar vergleicht. git diff ohne Parameter vergleicht Working Directory gegen Index, exakt die Änderungen, die ein nachfolgender git add erfassen würde. git diff --staged (Synonym --cached) vergleicht stattdessen Index gegen HEAD, exakt das, was der nächste Commit tatsächlich aufzeichnen würde. Wer beide verwechselt, sieht scheinbar keine Änderungen, obwohl der Index längst vom letzten Commit abweicht.


# git diff: Working Directory vs Index
$ echo "<?php echo 'v3';" > app/code/Mironsoft/SeoSuite/Helper.php
$ git diff -- app/code/Mironsoft/SeoSuite/Helper.php
diff --git a/app/code/Mironsoft/SeoSuite/Helper.php b/app/code/Mironsoft/SeoSuite/Helper.php
index 8f94139..a1c2e3f 100644
--- a/app/code/Mironsoft/SeoSuite/Helper.php
+++ b/app/code/Mironsoft/SeoSuite/Helper.php
-<?php echo 'v2';
+<?php echo 'v3';

# git diff --staged: Index vs HEAD (only what was already git add'ed)
$ git diff --staged -- app/code/Mironsoft/SeoSuite/Helper.php
diff --git a/app/code/Mironsoft/SeoSuite/Helper.php b/app/code/Mironsoft/SeoSuite/Helper.php
index e69de29..8f94139 100644
--- a/app/code/Mironsoft/SeoSuite/Helper.php
+++ b/app/code/Mironsoft/SeoSuite/Helper.php
-
+<?php echo 'v2';

5. Den Index direkt inspizieren mit git ls-files

git ls-files macht den Index ohne Umwege sichtbar. Der Flag -s (bzw. --stage) listet jeden Eintrag exakt so, wie er im Index steht: Modus, Blob-Hash, Stage-Nummer und Pfad, getrennt durch Tabs. -u zeigt nur unaufgelöste Merge-Konflikte mit ihren mehreren Stage-Einträgen, -m listet Pfade, deren Working-Directory-Inhalt laut Stat-Vergleich vom Index abweicht, und -o zusammen mit --exclude-standard zeigt untracked Dateien, die weder im Index noch von .gitignore erfasst werden.

Wie der Index solche Vergleiche technisch interpretiert, hängt zusätzlich von core-Einstellungen in der Konfiguration ab. core.filemode bestimmt, ob reine Berechtigungsänderungen (chmod +x) überhaupt als Modifikation zählen, wichtig auf Dateisystemen ohne echte Unix-Rechte wie manchen Netzwerk-Mounts. core.ignorecase steuert, ob Pfadvergleiche Groß- und Kleinschreibung ignorieren, relevant für Entwickler, die zwischen macOS und Linux wechseln. sparseCheckoutCone beeinflusst schließlich, welche Pfade im Index überhaupt einen Working-Tree-Eintrag erwarten.


# .git/config: [core] settings that change how the index behaves
[core]
	# If false, chmod +x on files is never recorded as a modification
	# (important on filesystems without real Unix permission bits)
	filemode = true

	# If true, "APP.PHP" and "app.php" are treated as the same index path
	# (relevant when switching a checkout between macOS and Linux)
	ignorecase = false

	# Enables sparse-checkout in cone mode: paths outside the cone stay
	# in the index but are marked skip-worktree, no file on disk expected
	sparseCheckoutCone = true

[extensions]
	# Newer index format extension that speeds up status on large repos
	worktreeConfig = true

6. Low-Level-Zugriff mit git update-index

git update-index ist die Low-Level-Schnittstelle, mit der Skripte den Index direkt manipulieren, ohne den Umweg über das Working Directory. Mit --add --cacheinfo <mode> <hash> <pfad> lässt sich ein bereits existierendes Blob-Objekt in den Index eintragen, selbst wenn im Arbeitsverzeichnis gar keine passende Datei liegt. Das nutzen etwa Merge-Treiber oder Build-Pipelines, die generierte Artefakte gezielt in einen Commit einschleusen wollen, ohne sie temporär auf die Festplatte zu schreiben.

Für die Performance auf großen Repositories bietet update-index zwei weitere Schalter: --assume-unchanged markiert einen Pfad so, dass Git dessen Stat-Daten beim Status-Check gar nicht erst prüft, praktisch für lokal überschriebene Konfigurationsdateien, die sich nie im Repository selbst ändern sollen. --skip-worktree geht einen Schritt weiter und wird zusätzlich von Sparse-Checkout-Mechanismen genutzt, um Pfade im Index zu behalten, ohne sie überhaupt im Working Directory zu erwarten. Beide Flags sind rein lokale Einstellungen und werden nicht mitcommittet.


# git update-index: insert a blob into the index without a worktree file
$ git hash-object -w --stdin <<< "return ['generated' => true];"
c7a1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9

$ git update-index --add --cacheinfo 100644 \
    c7a1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9 \
    generated/config-cache.php

$ git ls-files -s generated/config-cache.php
100644 c7a1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9 0	generated/config-cache.php

# git update-index: skip stat checks for a locally overridden file
$ git update-index --assume-unchanged src/app/etc/env.php
$ git status --short
# env.php no longer shows up as modified, even after local edits

# Undo it again when the file needs tracking normally
$ git update-index --no-assume-unchanged src/app/etc/env.php

7. Partielles Stagen mit git add -p

Partielles Stagen mit git add -p ist die praktische Konsequenz aus einer einzigen Tatsache: Der Index ist vollständig entkoppelt vom Working Directory und arbeitet nicht auf Dateiebene, sondern auf Blob-Ebene. Der Befehl zerlegt jede geänderte Datei in Hunks, einzelne zusammenhängende Änderungsblöcke, und fragt für jeden Hunk einzeln nach: y stagt den Hunk, n überspringt ihn, s versucht, einen zu großen Hunk in kleinere Teile zu splitten, und q bricht die gesamte Sitzung ab.

Technisch erzeugt add -p dabei intern einen Patch, der nur die ausgewählten Hunks enthält, wendet ihn über git apply --cached ausschließlich auf den Index an und lässt das Working Directory dabei komplett unberührt. Das Ergebnis ist ein neuer Blob, der weder dem alten committeten Stand noch dem aktuellen Dateiinhalt auf der Festplatte entspricht, sondern exakt der von Hand ausgewählten Zwischenversion. Genau das ermöglicht fokussierte, thematisch saubere Commits aus einer einzigen, unaufgeräumten Arbeitssitzung.


$ git add -p app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php
diff --git a/.../MetaGenerator.php b/.../MetaGenerator.php
@@ -12,6 +12,9 @@ class MetaGenerator
     public function generate(string $sku): string
     {
+        // Bug fix: trim whitespace before length check
+        $sku = trim($sku);
+
Stage this hunk [y,n,q,a,d,s,e,?]? s
Split into 2 hunks.
@@ -12,3 +12,4 @@ class MetaGenerator
     public function generate(string $sku): string
     {
+        $sku = trim($sku);
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y

@@ -20,6 +21,9 @@ class MetaGenerator
         return $meta;
     }
+
+    // New feature: separate log entry, unrelated to the bug fix above
+    private function logGeneration(string $sku): void { /* ... */ }
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? n

$ git diff --staged --stat
 MetaGenerator.php | 1 +
 1 file changed, 1 insertion(+)

8. Der Index während Merge-Konflikten: Stage 1, 2 und 3

Während eines Merge-Konflikts verlässt der Index kurzzeitig sein normales Schema. Statt eines einzigen Eintrags pro Pfad mit Stage 0 enthält er für jeden konfliktbehafteten Pfad bis zu drei Einträge gleichzeitig: Stage 1 für die gemeinsame Vorgängerversion (Merge-Base), Stage 2 für "ours", die Version des aktuellen Branches, und Stage 3 für "theirs", die Version des zu mergenden Branches. Erst wenn ein Konflikt aufgelöst und die Datei erneut mit git add erfasst wird, verschwinden die Stage-1-bis-3-Einträge zugunsten eines einzigen Stage-0-Eintrags.

git ls-files -u macht genau diese drei Versionen sichtbar, jeweils mit eigenem Blob-Hash. Darauf bauen git checkout --ours <datei> und git checkout --theirs <datei> auf: Sie kopieren gezielt den Blob aus Stage 2 beziehungsweise Stage 3 zurück ins Working Directory, ohne den Konflikt im Index selbst aufzulösen. Wer stattdessen den Inhalt manuell zusammenführt, muss am Ende trotzdem git add aufrufen, denn erst das reduziert die drei konkurrierenden Stage-Einträge wieder auf den regulären Stage-0-Zustand.

9. Index-Operationen im direkten Vergleich

Die folgende Tabelle stellt die in diesem Artikel behandelten Index-Operationen nebeneinander: was jeder Befehl liest, was er tatsächlich verändert, und wofür er sich im Entwickleralltag eignet. Sie macht sichtbar, dass die meisten Verwirrungen rund um den Index letztlich eine Frage der Lese- und Schreibrichtung sind.

Befehl Was gelesen wird Was geschrieben wird Typischer Einsatz
git add <pfad> Working Directory Neuer Blob + Index-Eintrag (Stage 0) Änderungen für den nächsten Commit vormerken
git status Working Directory, Index, HEAD Nichts Gesamtüberblick über alle drei Bereiche
git diff Working Directory vs. Index Nichts Vorschau, was ein add erfassen würde
git diff --staged Index vs. HEAD Nichts Vorschau, was ein commit speichern würde
git ls-files -s Nur der Index Nichts Rohdaten aller Index-Einträge inspizieren
git update-index --cacheinfo Vorhandener Blob-Hash Index-Eintrag direkt Skriptgesteuerte Index-Manipulation
git add -p Working Directory (hunkweise) Neuer Teil-Blob + Index-Eintrag Fokussierte, thematisch saubere Commits bauen
git checkout --ours <pfad> Stage-2-Eintrag im Index Working Directory Merge-Konflikt zugunsten der eigenen Version lösen

Wer diese Tabelle verinnerlicht, erkennt ein wiederkehrendes Muster: Lesende Befehle wie status, diff und ls-files verändern niemals den Index selbst, sie machen nur seinen aktuellen Zustand sichtbar. Schreibende Befehle wie add, add -p und update-index sind dagegen die einzigen Wege, wie sich der Inhalt von .git/index tatsächlich ändert, unabhängig davon, ob die Änderung über das Working Directory, einen Patch oder einen direkt angegebenen Hash erfolgt.

Mironsoft

Git-Workflows, Code-Reviews und CI/CD-Pipelines für PHP- und Magento-Teams

Sauberere Commits durch echtes Index-Verständnis?

Wir helfen Entwicklerteams, fokussierte Commit-Historien, saubere Merge-Konflikt-Resolution und Index-basierte Pre-Commit-Hooks aufzubauen, statt Git-Befehle nach Gefühl einzusetzen.

Git-Internals-Schulung

Index, Objekte und Trees praxisnah für Entwicklerteams erklärt

Workflow-Audit

Bestehende Staging- und Commit-Praxis analysieren und optimieren

CI/CD-Integration

Index-basierte Pre-Commit-Hooks und Linting-Pipelines aufsetzen

10. Zusammenfassung

Der Git-Index ist keine Metapher, sondern eine reale, binäre Datei unter .git/index mit dokumentiertem Format, Prüfsumme und sortierten Einträgen aus Pfad, Modus, Blob-Hash und Stage-Nummer. git add schreibt dabei tatsächlich einen neuen Blob nach .git/objects und aktualisiert den passenden Index-Eintrag inklusive Stat-Cache, lange bevor überhaupt ein Commit existiert. git commit verwandelt diesen Index-Zustand direkt in ein Tree-Objekt, ohne Zwischenschritt und ohne für unveränderte Dateien neue Blobs zu erzeugen.

git status und git diff sind letztlich nur Vergleichsoperationen zwischen Working Directory, Index und HEAD, während git ls-files und git update-index direkten Lese- und Schreibzugriff auf den Index selbst erlauben, unabhängig vom Working Directory. Genau diese Entkopplung macht git add -p möglich und erklärt, warum der Index während eines Merge-Konflikts kurzzeitig bis zu drei Versionen desselben Pfads gleichzeitig verwalten kann, adressiert über die Stage-Nummern 1, 2 und 3.

Der Git-Index auf einen Blick

Index-Format

.git/index ist eine binäre, versionierte Datei mit DIRC-Signatur, Prüfsumme und sortierten Einträgen aus Pfad, Modus, Hash und Stage.

git add

Schreibt einen neuen Blob nach .git/objects und aktualisiert den passenden Index-Eintrag inklusive Stat-Cache.

Inspektion

git ls-files -s/-u/-m/-o und git update-index machen den Index direkt sichtbar und manipulierbar.

Merge-Konflikte

Stage 1 bis 3 speichern Basis, ours und theirs gleichzeitig, bis git add sie auf Stage 0 reduziert.

11. FAQ: Der Git-Index im Detail

1Was ist der Git-Index technisch gesehen?
Eine reale binäre Datei unter .git/index mit dokumentiertem Format: Signatur DIRC, Versionsnummer, sortierte Einträge aus Pfad, Modus, Blob-Hash und Stage-Nummer, mit Prüfsumme am Ende.
2Was passiert technisch bei git add?
Schreibt den Dateiinhalt als neues Blob-Objekt nach .git/objects und aktualisiert den Index-Eintrag auf den neuen Hash sowie aktuelle Stat-Daten.
3Warum unterscheiden sich git diff und git diff --staged?
git diff vergleicht Working Directory gegen Index. git diff --staged (--cached) vergleicht Index gegen HEAD, den letzten Commit.
4Was zeigt git ls-files -s an?
Modus, Blob-Hash, Stage-Nummer und Pfad jedes Index-Eintrags, genau wie intern gespeichert.
5Wofür wird die Stage-Nummer im Index verwendet?
Stage 0 ist der Normalzustand. Stage 1 bis 3 treten nur bei Merge-Konflikten auf und speichern Basis, ours und theirs gleichzeitig.
6Was macht git update-index --add --cacheinfo?
Trägt einen vorhandenen Blob-Hash direkt als Index-Eintrag ein, ohne passende Datei im Working Directory. Nützlich für Skripte und Merge-Treiber.
7Wofür sind --assume-unchanged und --skip-worktree gut?
Überspringen Stat-Prüfungen für einen Pfad, etwa bei lokal überschriebenen Konfigurationsdateien oder Sparse-Checkout. Beide sind rein lokal.
8Wie funktioniert git add -p technisch?
Erzeugt einen Patch nur aus den ausgewählten Hunks und wendet ihn über git apply --cached ausschließlich auf den Index an.
9Was bedeuten die Stage-Nummern 1, 2, 3 bei einem Merge-Konflikt?
Stage 1 ist die gemeinsame Vorgängerversion, Stage 2 ours, Stage 3 theirs. git ls-files -u zeigt alle drei gleichzeitig.
10Ist der Index dasselbe wie die Staging Area?
Ja, beide bezeichnen dieselbe Datei .git/index. Staging Area ist der umgangssprachliche, Index der technische Name.