# Kapitel 1: Git-Interna verstehen – Wie Git wirklich funktioniert

**Einleitung:** Im [Einsteigerkurs](https://li.fabula.vision/git) hast du gelernt, Git zu benutzen – jetzt ist es Zeit zu verstehen, *wie* Git unter der Haube funktioniert. Git ist im Kern eine inhaltsadressierte Datenbank, die auf vier fundamentalen Objekttypen basiert: Blobs, Trees, Commits und Tags. Wenn du verstehst, wie diese zusammenhängen, kannst du Git-Probleme schneller diagnostizieren und fortgeschrittene Operationen selbstbewusst durchführen. Du wirst lernen, dass ein Commit nicht einfach „eine Änderung“ ist, sondern ein kompletter Schnappschuss mit Zeiger auf seinen Vorgänger. Dieses Wissen wird dir helfen, Befehle wie `<span>rebase</span>`, `<span>cherry-pick</span>` und `<span>reflog</span>` wirklich zu verstehen, anstatt sie nur auswendig zu lernen.

# Die vier fundamentalen Objekttypen in Git 🔍

Git ist im Kern nichts anderes als eine **inhaltsadressierte Datenbank** – ein elegantes System, das auf nur vier fundamentalen Objekttypen basiert. Wenn du verstehst, wie diese Objekte funktionieren und zusammenhängen, wirst du Git nie wieder als „magische Black Box" wahrnehmen, sondern als das durchdachte, transparente System, das es wirklich ist.

---

## Das Fundament: Gits Objektmodell

Jedes Git-Repository speichert seine gesamte Geschichte in Form von **Objekten** im Verzeichnis `.git/objects/`. Jedes Objekt wird durch einen **SHA-1-Hash** identifiziert – eine 40-stellige hexadezimale Zeichenkette, die aus dem Inhalt des Objekts berechnet wird. Das bedeutet: *Gleicher Inhalt ergibt immer den gleichen Hash*, egal auf welchem Computer oder in welchem Repository.

```mermaid
flowchart TB
    subgraph Objektdatenbank[".git/objects/"]
        B1["Blob\nfa3c2..."]
        B2["Blob\n8b137..."]
        B3["Blob\ne69de..."]
        T1["Tree\na1b2c..."]
        T2["Tree\nd4e5f..."]
        C1["Commit\n7f8d9..."]
        C2["Commit\n3a4b5..."]
        TAG["Tag\nc6d7e..."]
    end
    
    C1 --> T1
    C2 --> T2
    C2 -.->|parent| C1
    T1 --> B1
    T1 --> B2
    T1 --> T2
    T2 --> B3
    TAG --> C2
    
    style B1 fill:#e8f4e8,color:#000000
    style B2 fill:#e8f4e8,color:#000000
    style B3 fill:#e8f4e8,color:#000000
    style T1 fill:#fff3e0,color:#000000
    style T2 fill:#fff3e0,color:#000000
    style C1 fill:#e3f2fd,color:#000000
    style C2 fill:#e3f2fd,color:#000000
    style TAG fill:#fce4ec,color:#000000
```

---

## 1. Blob – Der Dateiinhalt 📄

Ein **Blob** (Binary Large Object) ist das einfachste Git-Objekt. Es speichert ausschließlich den **Inhalt einer Datei** – nichts weiter. Kein Dateiname, keine Berechtigungen, keine Metadaten. Nur der pure Inhalt.

### Eigenschaften des Blob-Objekts

- Speichert **rohen Dateiinhalt** (Text oder Binärdaten)
- Enthält **keinen Dateinamen** – der Name wird im Tree gespeichert
- Enthält **keine Zeitstempel** oder andere Metadaten
- **Identische Dateien** erzeugen identische Blobs (und damit denselben Hash)

Das Letztere ist besonders clever: Wenn du dieselbe Datei in zehn verschiedenen Verzeichnissen hast, speichert Git sie nur *einmal* als Blob. Alle Trees verweisen dann auf denselben Blob.

### Blob mit `git cat-file` untersuchen

Lass uns das praktisch ausprobieren. Erstelle zunächst ein Test-Repository:

```bash
# Neues Repository erstellen
mkdir git-objekte-demo && cd git-objekte-demo
git init

# Eine einfache Datei erstellen
echo "Hallo Git-Welt!" > hallo.txt
git add hallo.txt
```

Mit `git add` wurde die Datei in die Staging Area aufgenommen – und dabei wurde ein Blob-Objekt erstellt. Finde den Hash heraus:

```bash
# Hash des Blobs anzeigen (aus dem Index/Staging Area)
git ls-files -s
```

**Ausgabe:**

```
100644 d6a96ae3b442218a91512b9e1c57b9578b8998e8 0	hallo.txt
```

Die Zahl `100644` sind die Dateiberechtigungen, `d6a96ae...` ist der SHA-1-Hash des Blobs. Jetzt können wir den Blob untersuchen:

```bash
# Typ des Objekts anzeigen
git cat-file -t d6a96ae3b442218a91512b9e1c57b9578b8998e8
```

**Ausgabe:**

```
blob
```

```bash
# Inhalt des Blobs anzeigen
git cat-file -p d6a96ae3b442218a91512b9e1c57b9578b8998e8
```

**Ausgabe:**

```
Hallo Git-Welt!
```

```bash
# Größe des Blobs in Bytes
git cat-file -s d6a96ae3b442218a91512b9e1c57b9578b8998e8
```

**Ausgabe:**

```
16
```

> 💡 **Tipp:** Du musst nicht den vollständigen Hash eingeben. Git akzeptiert auch eindeutige Präfixe, z.B. `git cat-file -p d6a96ae` oder sogar `git cat-file -p d6a96`.

---

## 2. Tree – Das Verzeichnis 🌳

Ein **Tree** repräsentiert ein **Verzeichnis** in Git. Er ist quasi das „Inhaltsverzeichnis", das Dateinamen mit Blobs verknüpft. Ein Tree enthält eine Liste von Einträgen, wobei jeder Eintrag aus folgenden Komponenten besteht:

- **Modus** (Dateiberechtigungen, z.B. `100644` für normale Dateien, `100755` für ausführbare Dateien, `040000` für Unterverzeichnisse)
- **Objekttyp** (blob oder tree)
- **SHA-1-Hash** des referenzierten Objekts
- **Name** der Datei oder des Unterverzeichnisses

### Tree-Struktur visualisiert

```mermaid
flowchart TB
    ROOT["Tree: Wurzelverzeichnis\na1b2c3d..."]
    
    ROOT --> E1["100644 blob fa3c2...\nhallo.txt"]
    ROOT --> E2["100644 blob 8b137...\nREADME.md"]
    ROOT --> E3["040000 tree d4e5f...\nsrc/"]
    
    SRC["Tree: src/\nd4e5f6g..."]
    E3 -.-> SRC
    
    SRC --> E4["100644 blob e69de...\nindex.php"]
    SRC --> E5["100755 blob 12345...\nscript.sh"]
    
    style ROOT fill:#fff3e0,color:#000000
    style SRC fill:#fff3e0,color:#000000
    style E1 fill:#e8f4e8,color:#000000
    style E2 fill:#e8f4e8,color:#000000
    style E3 fill:#fff3e0,color:#000000
    style E4 fill:#e8f4e8,color:#000000
    style E5 fill:#e8f4e8,color:#000000
```

### Tree praktisch untersuchen

Erweitern wir unser Demo-Repository und erstellen einen Commit, damit wir Trees untersuchen können:

```bash
# Weitere Dateien und ein Verzeichnis erstellen
echo "# Mein Projekt" > README.md
mkdir src
echo "<?php echo 'Hello World';" > src/index.php
git add .
git commit -m "Erster Commit mit Projektstruktur"
```

Jetzt können wir den Tree des letzten Commits anschauen:

```bash
# Den Tree-Hash des letzten Commits finden
git cat-file -p HEAD
```

**Ausgabe:**

```
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
author Max Mustermann <max@example.com> 1703847600 +0100
committer Max Mustermann <max@example.com> 1703847600 +0100

Erster Commit mit Projektstruktur
```

Den Tree-Hash (hier beginnt er mit `4b825dc...`, dein Hash wird anders sein) können wir nun untersuchen:

```bash
# Tree-Inhalt anzeigen (ersetze den Hash durch deinen)
git cat-file -p 4b825dc642cb6eb9a060e54bf8d69288fbee4904
```

**Ausgabe:**

```
100644 blob d6a96ae3b442218a91512b9e1c57b9578b8998e8	hallo.txt
100644 blob 7e59600739c96546163833214c36459e324bad0a	README.md
040000 tree 8f94139338f9404f26296befa88755fc2598c289	src
```

Du siehst: Der Tree enthält Verweise auf zwei Blobs (die Dateien) und einen weiteren Tree (das `src`-Verzeichnis). Lass uns auch den Unter-Tree anschauen:

```bash
# Unter-Tree (src/) untersuchen
git cat-file -p 8f94139338f9404f26296befa88755fc2598c289
```

**Ausgabe:**

```
100644 blob 8b137891791fe96927ad78e64b0aad7bded08bdc	index.php
```

### Praktischer Shortcut mit `git ls-tree`

Für die Untersuchung von Trees gibt es auch den spezialisierten Befehl `git ls-tree`:

```bash
# Tree des aktuellen Commits rekursiv anzeigen
git ls-tree -r HEAD
```

**Ausgabe:**

```
100644 blob d6a96ae3b442218a91512b9e1c57b9578b8998e8	hallo.txt
100644 blob 7e59600739c96546163833214c36459e324bad0a	README.md
100644 blob 8b137891791fe96927ad78e64b0aad7bded08bdc	src/index.php
```

---

## 3. Commit – Der Schnappschuss 📸

Ein **Commit** ist das Herzstück von Git. Er repräsentiert einen **Schnappschuss** deines gesamten Projekts zu einem bestimmten Zeitpunkt. Wichtig zu verstehen: Ein Commit speichert *nicht* die Änderungen (Diffs), sondern verweist auf einen Tree, der den **kompletten Zustand** aller Dateien beschreibt.

### Bestandteile eines Commit-Objekts

| Feld | Beschreibung |
|------|--------------|
| `tree` | SHA-1-Hash des Root-Trees (Projekt-Schnappschuss) |
| `parent` | SHA-1-Hash des Vorgänger-Commits (kann fehlen beim ersten Commit, oder mehrfach vorhanden sein bei Merge-Commits) |
| `author` | Name, E-Mail und Zeitstempel der Person, die die Änderung ursprünglich erstellt hat |
| `committer` | Name, E-Mail und Zeitstempel der Person, die den Commit erstellt hat (kann bei Cherry-Picks/Rebases abweichen) |
| Leerzeile | Trennt Header von der Nachricht |
| Commit-Message | Die Beschreibung der Änderung |

### Commit praktisch untersuchen

```bash
# Einen zweiten Commit erstellen
echo "Neue Zeile" >> hallo.txt
git add hallo.txt
git commit -m "hallo.txt erweitert"

# Die letzten beiden Commits anzeigen
git log --oneline -2
```

**Ausgabe:**

```
a7b3c9d (HEAD -> main) hallo.txt erweitert
f1e2d3c Erster Commit mit Projektstruktur
```

Jetzt untersuchen wir den neuesten Commit:

```bash
git cat-file -p HEAD
```

**Ausgabe:**

```
tree 9f8e7d6c5b4a3210fedcba9876543210abcdef12
parent f1e2d3c4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0
author Max Mustermann <max@example.com> 1703851200 +0100
committer Max Mustermann <max@example.com> 1703851200 +0100

hallo.txt erweitert
```

Du siehst hier den **parent**-Eintrag, der auf den vorherigen Commit verweist. Diese Verkettung durch Parent-Referenzen bildet die **Commit-Historie** – eine einfach verkettete Liste (oder bei Merges: ein gerichteter azyklischer Graph).

### Die Commit-Kette visualisiert

```mermaid
flowchart RL
    C3["Commit C3\na7b3c9d\n\nhallo.txt erweitert"]
    C2["Commit C2\nf1e2d3c\n\nErster Commit"]
    C1["Commit C1\n0a1b2c3\n\nInitial commit"]
    
    C3 -->|parent| C2
    C2 -->|parent| C1
    
    HEAD["HEAD"]
    MAIN["main"]
    
    HEAD -.-> MAIN
    MAIN -.-> C3
    
    style C3 fill:#e3f2fd,color:#000000
    style C2 fill:#e3f2fd,color:#000000
    style C1 fill:#e3f2fd,color:#000000
    style HEAD fill:#ffecb3,color:#000000
    style MAIN fill:#c8e6c9,color:#000000
```

### Merge-Commits haben mehrere Parents

Bei einem Merge-Commit gibt es mehrere `parent`-Einträge:

```bash
# Beispiel eines Merge-Commits (nach einem Merge erstellt)
git cat-file -p <merge-commit-hash>
```

**Ausgabe:**

```
tree 1234567890abcdef...
parent aaaaaaa111111...   <-- erster Parent (der Branch, IN den gemerged wurde)
parent bbbbbbb222222...   <-- zweiter Parent (der Branch, DER gemerged wurde)
author Max Mustermann <max@example.com> 1703854800 +0100
committer Max Mustermann <max@example.com> 1703854800 +0100

Merge branch 'feature/login'
```

---

## 4. Tag – Die benannte Markierung 🏷️

Ein **Tag** ist ein benannter Zeiger auf einen bestimmten Commit – typischerweise verwendet für Release-Versionen wie `v1.0.0` oder `v2.3.1`. Git kennt zwei Arten von Tags:

### Lightweight Tags vs. Annotated Tags

| Eigenschaft | Lightweight Tag | Annotated Tag |
|------------|-----------------|---------------|
| **Speicherung** | Nur eine Referenz in `.git/refs/tags/` | Eigenes Tag-Objekt in `.git/objects/` |
| **Metadaten** | Keine | Tagger, Datum, Nachricht |
| **GPG-Signatur** | Nicht möglich | Möglich |
| **Empfehlung** | Für temporäre/lokale Markierungen | Für Releases und wichtige Meilensteine |

### Annotated Tag erstellen und untersuchen

```bash
# Annotated Tag erstellen
git tag -a v1.0.0 -m "Erste stabile Version"

# Tag-Objekt untersuchen
git cat-file -t v1.0.0
```

**Ausgabe:**

```
tag
```

```bash
git cat-file -p v1.0.0
```

**Ausgabe:**

```
object a7b3c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
type commit
tag v1.0.0
tagger Max Mustermann <max@example.com> 1703858400 +0100

Erste stabile Version
```

Das Tag-Objekt enthält:
- **object**: Der SHA-1-Hash des Commits, auf den der Tag zeigt
- **type**: Der Typ des referenzierten Objekts (normalerweise `commit`)
- **tag**: Der Name des Tags
- **tagger**: Wer den Tag wann erstellt hat
- **Nachricht**: Die Tag-Beschreibung

### Lightweight Tag zum Vergleich

```bash
# Lightweight Tag erstellen
git tag v1.0.0-beta

# Typ prüfen - zeigt direkt auf den Commit!
git cat-file -t v1.0.0-beta
```

**Ausgabe:**

```
commit
```

Ein Lightweight Tag ist kein eigenes Objekt, sondern nur eine Referenz-Datei. Wenn du `git cat-file -p v1.0.0-beta` ausführst, siehst du direkt den Commit-Inhalt, nicht ein Tag-Objekt.

---

## Wie alles zusammenhängt 🔗

Jetzt, wo du alle vier Objekttypen kennst, lass uns das große Ganze betrachten. Ein typisches Git-Repository mit zwei Commits könnte intern so aussehen:

```mermaid
flowchart TB
    subgraph Referenzen
        HEAD["HEAD\n→ refs/heads/main"]
        MAIN["refs/heads/main\n→ commit 2"]
        TAG["refs/tags/v1.0.0\n→ tag-objekt"]
    end
    
    subgraph Objekte
        TAGO["Tag-Objekt\nv1.0.0\n→ commit 2"]
        
        C2["Commit 2\ntree: T2\nparent: C1"]
        C1["Commit 1\ntree: T1\nparent: keine"]
        
        T2["Tree T2\nhallo.txt → B2\nREADME.md → B3\nsrc/ → T3"]
        T1["Tree T1\nhallo.txt → B1\nREADME.md → B3"]
        T3["Tree T3\nindex.php → B4"]
        
        B1["Blob B1\nHallo Git-Welt!"]
        B2["Blob B2\nHallo Git-Welt!\nNeue Zeile"]
        B3["Blob B3\n# Mein Projekt"]
        B4["Blob B4\nPHP-Code..."]
    end
    
    HEAD -.-> MAIN
    MAIN -.-> C2
    TAG -.-> TAGO
    TAGO --> C2
    
    C2 --> T2
    C2 -->|parent| C1
    C1 --> T1
    
    T2 --> B2
    T2 --> B3
    T2 --> T3
    T1 --> B1
    T1 --> B3
    T3 --> B4
    
    style HEAD fill:#ffecb3,color:#000000
    style MAIN fill:#c8e6c9,color:#000000
    style TAG fill:#c8e6c9,color:#000000
    style TAGO fill:#fce4ec,color:#000000
    style C1 fill:#e3f2fd,color:#000000
    style C2 fill:#e3f2fd,color:#000000
    style T1 fill:#fff3e0,color:#000000
    style T2 fill:#fff3e0,color:#000000
    style T3 fill:#fff3e0,color:#000000
    style B1 fill:#e8f4e8,color:#000000
    style B2 fill:#e8f4e8,color:#000000
    style B3 fill:#e8f4e8,color:#000000
    style B4 fill:#e8f4e8,color:#000000
```

**Beachte die Effizienz:** Die Datei `README.md` hat sich zwischen Commit 1 und Commit 2 nicht geändert. Daher verweisen beide Trees auf **denselben Blob** (B3). Git speichert den Inhalt nur einmal!

---

## Die wichtigsten `git cat-file`-Optionen im Überblick

| Option | Beschreibung | Beispiel |
|--------|--------------|----------|
| `-t` | Zeigt den **Typ** des Objekts | `git cat-file -t HEAD` → `commit` |
| `-p` | **Pretty-Print**: Zeigt den Inhalt formatiert an | `git cat-file -p HEAD` |
| `-s` | Zeigt die **Größe** in Bytes | `git cat-file -s HEAD` |
| `-e` | **Existenz-Check**: Exit-Code 0 wenn Objekt existiert | `git cat-file -e abc123` |

### Nützliche Kombinationen und Shortcuts

```bash
# Alle Objekte im Repository auflisten
find .git/objects -type f | head -20

# Typ und Größe aller Objekte eines Commits anzeigen
git rev-list --objects HEAD | while read hash name; do
  echo "$hash $(git cat-file -t $hash) $(git cat-file -s $hash) $name"
done

# Direkter Zugriff auf Tree eines Commits
git cat-file -p HEAD^{tree}

# Direkter Zugriff auf eine Datei in einem Commit
git cat-file -p HEAD:src/index.php
```

---

## Praktische Übung zum Selbermachen 🎯

Um das Gelernte zu festigen, probiere Folgendes in einem Test-Repository:

1. **Erstelle ein neues Repository** mit einigen Dateien und Unterverzeichnissen
   
2. **Finde die Blob-Hashes** deiner Dateien mit `git ls-files -s`

3. **Untersuche den Tree** deines letzten Commits mit `git cat-file -p HEAD^{tree}`

4. **Vergleiche zwei Commits**: Erstelle eine kleine Änderung, committe sie, und vergleiche die Tree-Hashes beider Commits. Welche Blobs haben sich geändert, welche sind gleich geblieben?

5. **Erstelle einen Annotated Tag** und untersuche sein Objekt

Mit diesem Wissen über Git-Interna bist du bestens vorbereitet für die fortgeschrittenen Themen wie Rebase, Reflog und Troubleshooting – denn du verstehst jetzt, *was* Git eigentlich tut, wenn du diese Befehle ausführst! 🚀

# Git als inhaltsadressierte Datenbank 🔐

Git unterscheidet sich fundamental von anderen Versionskontrollsystemen durch seine Architektur als **inhaltsadressierte Datenbank** (content-addressable storage). Dieses Konzept ist der Schlüssel, um zu verstehen, warum Git so effizient, zuverlässig und schnell ist – und warum bestimmte Operationen überhaupt möglich sind.

---

## Was „inhaltsadressiert" bedeutet

Bei einer **inhaltsadressierten Datenbank** wird der Speicherort eines Objekts nicht durch einen Namen, einen Pfad oder eine fortlaufende Nummer bestimmt, sondern **direkt durch den Inhalt selbst**. Genauer gesagt: Git berechnet aus dem Inhalt einer Datei (oder eines anderen Objekts) einen eindeutigen **kryptografischen Hash** und verwendet diesen Hash als „Adresse" oder Identifikator.

Das steht im Gegensatz zu herkömmlichen Dateisystemen:

| Eigenschaft | Klassisches Dateisystem | Git (inhaltsadressiert) |
|-------------|------------------------|-------------------------|
| **Identifikation** | Pfad und Dateiname | Hash des Inhalts |
| **Umbenennung** | Ändert den Identifikator | Hash bleibt gleich |
| **Gleicher Inhalt, zwei Orte** | Zwei separate Speicherungen | Nur einmal gespeichert |
| **Inhalt geändert** | Gleicher Name, neuer Inhalt | Neuer Hash = neues Objekt |

Der entscheidende Gedanke ist: **Der Inhalt *ist* die Adresse.** Wenn du den Inhalt kennst, kennst du auch, wo und wie er gespeichert ist. Und umgekehrt: Wenn du den Hash hast, weißt du exakt, welcher Inhalt dahintersteckt.

---

## Das SHA-1-Hash-System im Detail

### Was ist ein Hash?

Ein **Hash** (auch „Prüfsumme" oder „Fingerabdruck") ist das Ergebnis einer mathematischen Funktion, die eine beliebig große Eingabe auf eine Ausgabe fester Länge abbildet. Git verwendet dafür den **SHA-1-Algorithmus** (Secure Hash Algorithm 1), der einen 160-Bit-Hash erzeugt, dargestellt als 40-stellige Hexadezimalzahl.

**Beispiel eines Git-Hash:**

```
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
```

### Eigenschaften von SHA-1

SHA-1 hat mehrere wichtige Eigenschaften, die es für Git ideal machen:

1. **Deterministisch:** Derselbe Inhalt erzeugt *immer* denselben Hash – auf jedem Computer, zu jeder Zeit, in jedem Repository weltweit.

2. **Kollisionsresistent:** Es ist praktisch unmöglich, zwei verschiedene Inhalte zu finden, die denselben Hash erzeugen. Bei 160 Bit gibt es $2^{160}$ mögliche Hashes, das sind etwa $1{,}46 \times 10^{48}$ verschiedene Werte.

3. **Einweg-Funktion:** Aus dem Hash kann man den ursprünglichen Inhalt nicht rekonstruieren. Der Hash verrät nichts über den Inhalt außer seiner Identität.

4. **Lawineneffekt:** Selbst kleinste Änderungen am Inhalt führen zu einem völlig anderen Hash:

   ```
   Inhalt: "Hello World"
   Hash:   0a4d55a8d778e5022fab701977c5d840bbc486d0
   
   Inhalt: "Hello World!" (nur ein Ausrufezeichen hinzugefügt)
   Hash:   2ef7bde608ce5404e97d5f042f95f89f1c232871
   ```

### Wie Git den Hash berechnet

Git berechnet den Hash nicht einfach nur vom reinen Dateiinhalt, sondern fügt einen **Header** hinzu, der den Objekttyp und die Größe enthält:

```
<typ> <größe>\0<inhalt>
```

Für eine Datei mit dem Inhalt „Hello World" (11 Bytes) würde Git also hashen:

```
blob 11\0Hello World
```

Du kannst das selbst überprüfen:

```bash
# Manuell den Hash berechnen
echo -n "Hello World" | git hash-object --stdin
# Ergebnis: 5e1c309dae7f45e0f39b1bf3ac3cd9db12e7d689

# Zum Vergleich mit Unix-Tools
printf "blob 11\0Hello World" | sha1sum
# Gleiches Ergebnis!
```

### Der Übergang zu SHA-256

Git arbeitet seit einiger Zeit an einer Migration zu **SHA-256**, da SHA-1 theoretische Schwächen zeigt (2017 wurde die erste praktische Kollision demonstriert). Für die allermeisten Anwendungsfälle ist SHA-1 aber weiterhin sicher, und das Konzept der Inhaltsadressierung bleibt identisch.

---

## Warum identische Dateien denselben Hash haben

Hier liegt die Magie des Systems: Da der Hash **ausschließlich vom Inhalt** abhängt, erzeugen identische Inhalte *garantiert* identische Hashes – unabhängig davon:

- **Wer** den Hash berechnet
- **Wann** der Hash berechnet wird
- **Wo** (in welchem Repository) der Hash berechnet wird
- **Wie** die Datei heißt
- **In welchem Verzeichnis** die Datei liegt

### Ein konkretes Beispiel

Stell dir vor, du und ein Kollege arbeiten an verschiedenen Projekten auf verschiedenen Kontinenten, ohne voneinander zu wissen:

```
# Dein Repository in Berlin
projekt-alpha/
└── src/
    └── utils.php    # Inhalt: "<?php echo 'Hello';"

# Repository deines Kollegen in Tokyo
projekt-beta/
└── lib/
    └── helper.php   # Inhalt: "<?php echo 'Hello';"
```

Obwohl die Dateien unterschiedliche Namen haben und in komplett unverbundenen Projekten liegen, haben sie **exakt denselben Git-Hash**, weil ihr Inhalt identisch ist.

Du kannst das selbst testen:

```bash
# In deinem Repository
echo "<?php echo 'Hello';" > test1.php
git hash-object test1.php
# Ergebnis: z.B. 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

# In einem komplett anderen Repository auf einem anderen Computer
echo "<?php echo 'Hello';" > andere_datei.php
git hash-object andere_datei.php
# Gleiches Ergebnis: 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
```

### Was *nicht* in den Hash einfließt

Es ist wichtig zu verstehen, was alles **keinen Einfluss** auf den Hash einer Datei hat:

- ❌ Der Dateiname
- ❌ Der Verzeichnispfad
- ❌ Die Dateiberechtigungen (außer dem Executable-Bit)
- ❌ Der Zeitstempel der Datei
- ❌ Der Autor oder Committer
- ❌ Das Repository, in dem die Datei liegt
- ❌ Die Git-Version

Nur der **reine Dateiinhalt** (Byte für Byte) bestimmt den Hash.

---

## Praktische Auswirkungen

Die Inhaltsadressierung hat weitreichende Konsequenzen für die tägliche Arbeit mit Git:

### 1. Automatische Deduplizierung 💾

Git speichert identischen Inhalt nur **einmal**, egal wie oft er vorkommt. Wenn du dieselbe Datei in 100 verschiedenen Commits hast und sie sich nie ändert, existiert sie physisch nur einmal im `.git/objects`-Verzeichnis.

```
# Beispiel: Eine Datei in 100 Commits
# Naiver Ansatz: 100 × 10 KB = 1 MB Speicher
# Git: 1 × 10 KB = 10 KB Speicher
```

Das gilt auch für Dateien, die kopiert oder umbenannt werden: Solange der Inhalt gleich bleibt, wird nichts doppelt gespeichert.

### 2. Extrem effizientes Klonen und Fetching 🚀

Wenn du ein Repository klonst oder Änderungen fetchst, muss Git nur die Objekte übertragen, die du noch nicht hast. Durch den Hash kann Git sofort feststellen: „Dieses Objekt habe ich schon" – ohne den Inhalt vergleichen zu müssen.

```
# Beim git fetch:
# Dein lokales Repo: "Ich habe Objekte a1b2c3, d4e5f6, g7h8i9"
# Remote antwortet: "Der neue Commit braucht a1b2c3, x9y8z7"
# Git: "a1b2c3 habe ich schon, nur x9y8z7 muss übertragen werden"
```

### 3. Integrität und Datenvalidierung ✅

Der Hash dient als **kryptografische Prüfsumme**. Wenn auch nur ein einziges Bit in einem Objekt verändert wird (durch Festplattenfehler, Übertragungsprobleme oder absichtliche Manipulation), stimmt der Hash nicht mehr. Git kann das sofort erkennen:

```bash
# Git prüft automatisch bei vielen Operationen
# Du kannst es auch manuell auslösen:
git fsck
# Überprüft die Integrität aller Objekte

# Mögliche Ausgabe bei Problemen:
# error: sha1 mismatch for objects/a1/b2c3...
# fatal: loose object a1b2c3... is corrupt
```

Diese Eigenschaft macht Git extrem robust gegen:

- Festplattenfehler
- Netzwerkprobleme beim Push/Pull
- Versehentliche oder absichtliche Manipulation
- Bitrot (schleichende Datenkorruption)

### 4. Verteilte Zusammenarbeit ohne zentrale Autorität 🌍

Da Hashes deterministisch sind, braucht Git keine zentrale Instanz, die Objekte nummeriert oder IDs vergibt. Zwei Entwickler können unabhängig voneinander arbeiten, und wenn sie später ihre Repositories zusammenführen, sind identische Objekte automatisch „dieselben" – ohne Konflikte.

```mermaid
flowchart TD
    subgraph Alice["Alice's Repository"]
        A1[("Commit abc123")]
        A2[("Blob def456")]
    end
    
    subgraph Bob["Bob's Repository"]
        B1[("Commit xyz789")]
        B2[("Blob def456")]
    end
    
    subgraph Merged["Nach dem Merge"]
        M1[("Commit abc123")]
        M2[("Commit xyz789")]
        M3[("Blob def456\nnur einmal!")]
    end
    
    Alice --> Merged
    Bob --> Merged
    
    style A2 fill:#90EE90,color:#000000
    style B2 fill:#90EE90,color:#000000
    style M3 fill:#90EE90,color:#000000
```

### 5. Sichere Referenzierung von History 🔒

Da jeder Commit einen Hash hat, der von seinem Inhalt *und* von seinen Parent-Commits abhängt, ist die gesamte Git-History kryptografisch verkettet – ähnlich wie eine Blockchain:

```mermaid
flowchart LR
    C1["Commit A\nhash: a1b2c3"]
    C2["Commit B\nhash: d4e5f6\nparent: a1b2c3"]
    C3["Commit C\nhash: g7h8i9\nparent: d4e5f6"]
    
    C1 --> C2 --> C3
```

Wenn jemand versucht, einen alten Commit zu verändern, ändert sich sein Hash. Da der nächste Commit auf diesen Hash verweist, müsste auch dieser Commit neu gehasht werden – und so weiter bis zum aktuellen Commit. Eine **nachträgliche Manipulation der History ist sofort erkennbar**.

### 6. Commits sind global eindeutig 🆔

Die Wahrscheinlichkeit einer Hash-Kollision (dass zwei verschiedene Inhalte denselben Hash erzeugen) ist astronomisch gering:

$$
P(\text{Kollision}) \approx \frac{n^2}{2 \times 2^{160}}
$$

Selbst bei einer Milliarde Objekte ($n = 10^9$) ist die Wahrscheinlichkeit etwa:

$$
P \approx \frac{(10^9)^2}{2 \times 2^{160}} \approx 3{,}4 \times 10^{-31}
$$

Das bedeutet: Commit-Hashes sind praktisch **weltweit eindeutig**. Du kannst einen Hash wie `a1b2c3d4` als absoluten Identifikator verwenden, der niemals mit einem anderen Commit kollidieren wird.

### 7. Effizientes Branching und Tagging 🏷️

Branches und Tags in Git sind extrem „billig", weil sie nur **40-Byte-Zeiger** auf einen Commit-Hash sind:

```bash
# Ein Branch ist nur eine Datei mit einem Hash
cat .git/refs/heads/main
# Ausgabe: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0

# Ein Tag ist ähnlich simpel
cat .git/refs/tags/v1.0.0
# Ausgabe: f1e2d3c4b5a6978... (ein Hash)
```

Das Erstellen eines neuen Branches ist daher eine **O(1)**-Operation – es wird nur eine 40-Byte-Datei geschrieben, egal wie groß das Repository ist.

### 8. Rename Detection funktioniert automatisch 📁

Git speichert keine „Rename"-Operationen explizit. Wenn du eine Datei umbenennst, erkennt Git das beim Betrachten der History automatisch, weil **der Blob-Hash identisch bleibt**:

```bash
# Datei umbenennen
mv old_name.php new_name.php
git add -A
git commit -m "Rename file"

# Git erkennt das Rename anhand des identischen Hashes
git log --follow --stat new_name.php
# Zeigt die komplette History, auch vor dem Rename!
```

---

## Praktische Demonstration 🧪

Lass uns das Konzept in der Praxis erleben:

### Schritt 1: Zwei Dateien mit gleichem Inhalt erstellen

```bash
# Neues Test-Repository
mkdir hash-demo && cd hash-demo
git init

# Zwei Dateien mit identischem Inhalt, aber verschiedenen Namen
echo "Identischer Inhalt" > datei1.txt
echo "Identischer Inhalt" > datei2.txt

# Hashes anzeigen (ohne zu committen)
git hash-object datei1.txt
# Ausgabe: z.B. 9c59e24b8b0c4c2a8d3e5f6a7b8c9d0e1f2a3b4c

git hash-object datei2.txt
# Ausgabe: 9c59e24b8b0c4c2a8d3e5f6a7b8c9d0e1f2a3b4c  ← Identisch!
```

### Schritt 2: Änderung demonstrieren

```bash
# Winzige Änderung an datei2
echo "Identischer Inhalt!" > datei2.txt  # Ausrufezeichen hinzugefügt

git hash-object datei2.txt
# Ausgabe: 7f8e9d0c1b2a3456...  ← Komplett anderer Hash!
```

### Schritt 3: Objekte im Repository inspizieren

```bash
# Dateien zum Staging hinzufügen
git add datei1.txt
git add datei2.txt
git commit -m "Initial commit"

# Blobs im Repository anschauen
find .git/objects -type f
# Zeigt die gespeicherten Objekte

# Einen Blob inspizieren
git cat-file -t 9c59e24  # Typ anzeigen → "blob"
git cat-file -p 9c59e24  # Inhalt anzeigen → "Identischer Inhalt"
```

---

## Zusammenfassung 📋

Die Inhaltsadressierung ist das architektonische Fundament von Git:

| Aspekt | Bedeutung |
|--------|-----------|
| **Identität** | Inhalt = Adresse (Hash) |
| **Effizienz** | Automatische Deduplizierung |
| **Integrität** | Kryptografische Prüfsummen |
| **Verteilung** | Keine zentrale ID-Vergabe nötig |
| **Performance** | Schnelles Vergleichen via Hash |
| **Zuverlässigkeit** | Manipulation sofort erkennbar |

Wenn du dieses Konzept verinnerlicht hast, werden viele Git-Mechanismen plötzlich logisch: Warum Branches so billig sind, warum `git gc` Objekte aufräumen kann, warum Rebasing die History „umschreibt" und warum das Ändern alter Commits problematisch ist. Der Hash ist der Schlüssel zu allem. 🔑

# Git-Referenzen, Branches und HEAD – Was wirklich passiert 🔍

Um Git wirklich zu verstehen, musst du begreifen, dass **Branches keine Kopien deines Codes** sind – sie sind lediglich winzige Textdateien, die auf einen Commit zeigen. Dieses Kapitel enthüllt die elegante Einfachheit, die hinter Gits scheinbar komplexem Branching-System steckt.

---

## Das Konzept der Referenzen (refs)

Eine **Referenz** (kurz: *ref*) ist in Git nichts anderes als ein **lesbarer Name für einen SHA-1-Hash**. Anstatt dir kryptische Hashes wie `a1b2c3d4e5f6...` merken zu müssen, kannst du einfach `main`, `feature/login` oder `v1.0.0` verwenden.

### Was refs technisch sind

Jede Referenz ist eine **simple Textdatei**, die genau 40 Zeichen enthält – den SHA-1-Hash eines Commits. Das ist alles. Keine Magie, keine komplexe Datenstruktur, nur eine Zeile Text.

```
a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4
```

### Die verschiedenen Arten von Referenzen

Git kennt mehrere Kategorien von Referenzen, die alle im `.git/`-Verzeichnis organisiert sind:

| Referenz-Typ | Speicherort | Beispiel | Zweck |
|--------------|-------------|----------|-------|
| **Lokale Branches** | `.git/refs/heads/` | `refs/heads/main` | Deine lokalen Entwicklungszweige |
| **Remote-Tracking-Branches** | `.git/refs/remotes/` | `refs/remotes/origin/main` | Abbild der Branches auf dem Remote |
| **Tags** | `.git/refs/tags/` | `refs/tags/v1.0.0` | Benannte Marker für Releases |
| **HEAD** | `.git/HEAD` | – | Zeigt auf den aktuellen Branch |
| **Stash** | `.git/refs/stash` | – | Zwischengespeicherte Änderungen |

---

## Die Anatomie des `.git/refs/`-Verzeichnisses

Schauen wir uns an, wie ein typisches Repository strukturiert ist:

```
.git/
├── HEAD                    ← Zeigt auf aktuellen Branch
├── refs/
│   ├── heads/              ← Alle lokalen Branches
│   │   ├── main            ← Der main-Branch (eine Textdatei!)
│   │   ├── develop         ← Der develop-Branch
│   │   └── feature/
│   │       └── login       ← Feature-Branch mit Unterordner
│   ├── remotes/
│   │   └── origin/         ← Remote-Tracking-Branches
│   │       ├── main
│   │       └── develop
│   └── tags/
│       ├── v1.0.0          ← Lightweight Tag
│       └── v2.0.0          ← Annotated Tag (zeigt auf Tag-Objekt)
└── packed-refs             ← Optimierte Speicherung vieler Refs
```

### Praktische Untersuchung in deinem Repository

Du kannst dir die Refs direkt im Dateisystem ansehen:

```bash
# Zeige den Inhalt des main-Branch (nur der Hash!)
cat .git/refs/heads/main
# Ausgabe: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4

# Liste alle lokalen Branches als Dateien
ls -la .git/refs/heads/

# Zeige alle Referenzen mit git
git show-ref
```

### Die `packed-refs`-Datei

Bei Repositories mit vielen Branches und Tags optimiert Git die Speicherung, indem es Referenzen in einer einzigen Datei zusammenfasst:

```bash
cat .git/packed-refs
```

```
# pack-refs with: peeled fully-peeled sorted
a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 refs/heads/main
b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5 refs/heads/develop
c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6 refs/tags/v1.0.0
```

Git sucht Referenzen zuerst in den einzelnen Dateien unter `refs/` und dann in `packed-refs`. Wenn du einen Branch aktualisierst, wird eine neue Einzeldatei erstellt, die Vorrang vor dem Eintrag in `packed-refs` hat.

---

## Branches – Nichts als bewegliche Zeiger

Ein **Branch** ist eine Referenz, die automatisch mitwandert, wenn du neue Commits erstellst. Das unterscheidet Branches von Tags, die fest an einem Commit verankert bleiben.

### Was einen Branch ausmacht

```mermaid
gitGraph
    commit id: "C1"
    commit id: "C2"
    branch feature/login
    commit id: "C3"
    commit id: "C4"
    checkout main
    commit id: "C5"
```

In diesem Diagramm sind `main` und `feature/login` **keine Kopien des Codes** – sie sind lediglich zwei 40-Byte-Dateien, die auf verschiedene Commits zeigen:

- `.git/refs/heads/main` → zeigt auf C5
- `.git/refs/heads/feature/login` → zeigt auf C4

### Was passiert, wenn du einen Branch erstellst?

Wenn du `git branch feature/login` ausführst, passiert **erstaunlich wenig**:

1. **Git ermittelt den aktuellen Commit** – den Hash, auf den HEAD (über den aktuellen Branch) zeigt
2. **Git erstellt eine neue Datei** unter `.git/refs/heads/feature/login`
3. **Git schreibt den Hash** in diese Datei

Das ist alles! Kein Kopieren von Dateien, kein Duplizieren von Historie. Ein Branch zu erstellen ist deshalb in Git **instantan und kostet praktisch keinen Speicherplatz** – egal wie groß dein Repository ist.

```bash
# Diese beiden Befehle sind funktional identisch:

# Der "offizielle" Weg:
git branch feature/login

# Was Git intern macht (vereinfacht):
git rev-parse HEAD > .git/refs/heads/feature/login
```

### Demonstration: Branch manuell erstellen

Du kannst tatsächlich einen Branch „von Hand" erstellen:

```bash
# Aktuellen Commit-Hash ermitteln
git rev-parse HEAD
# Ausgabe: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4

# Branch manuell erstellen (nicht empfohlen, aber lehrreich!)
echo "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" > .git/refs/heads/mein-manueller-branch

# Überprüfen, ob es funktioniert hat
git branch
# Ausgabe enthält jetzt: mein-manueller-branch
```

---

## HEAD – Der spezielle Zeiger auf „Wo bin ich jetzt?"

**HEAD** ist die wichtigste Referenz in Git. Sie beantwortet die Frage: *„In welchem Branch arbeite ich gerade, und welchen Commit sehe ich in meinem Working Directory?"*

### HEAD ist (meistens) eine symbolische Referenz

Im Normalfall enthält `.git/HEAD` keinen direkten Commit-Hash, sondern einen **Verweis auf einen Branch**:

```bash
cat .git/HEAD
# Ausgabe: ref: refs/heads/main
```

Das bedeutet: *„HEAD zeigt auf den Branch main, und main zeigt auf einen bestimmten Commit."* Diese Indirektion ist wichtig, denn sie ermöglicht es Git, den Branch automatisch mitzubewegen, wenn du einen neuen Commit erstellst.

### Die HEAD-Kette visualisiert

```mermaid
flowchart LR
    HEAD["HEAD\n.git/HEAD"]
    main["main\n.git/refs/heads/main"]
    commit["Commit\na1b2c3d4..."]
    
    HEAD -->|"ref: refs/heads/main"| main
    main -->|"a1b2c3d4..."| commit
    
    style HEAD fill:#e1f5fe,color:#000000
    style main fill:#fff3e0,color:#000000
    style commit fill:#f3e5f5,color:#000000
```

### Detached HEAD – Wenn HEAD direkt auf einen Commit zeigt

Manchmal zeigt HEAD **direkt auf einen Commit** statt auf einen Branch. Das nennt man „Detached HEAD":

```bash
# Einen bestimmten Commit auschecken (nicht einen Branch)
git checkout a1b2c3d4

cat .git/HEAD
# Ausgabe: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4
```

Im Detached HEAD-Zustand zeigt HEAD nicht mehr auf `ref: refs/heads/...`, sondern enthält direkt den Hash. **Neue Commits, die du jetzt machst, gehören zu keinem Branch** und können verloren gehen, wenn du den Branch wechselst!

```mermaid
flowchart TB
    subgraph normal["Normaler Zustand"]
        HEAD1["HEAD"] --> main1["main"] --> C1["Commit C1"]
    end
    
    subgraph detached["Detached HEAD"]
        HEAD2["HEAD"] --> C2["Commit C2"]
        main2["main"] --> C3["Commit C3"]
    end
    
    style HEAD1 fill:#c8e6c9,color:#000000
    style HEAD2 fill:#ffcdd2,color:#000000
    style main1 fill:#fff3e0,color:#000000
    style main2 fill:#fff3e0,color:#000000
```

---

## Was passiert beim Branch-Wechsel? 🔄

Wenn du `git checkout feature/login` oder `git switch feature/login` ausführst, passieren **mehrere Dinge**:

### Schritt 1: Git aktualisiert HEAD

```bash
# Vorher:
cat .git/HEAD
# ref: refs/heads/main

# Nach git switch feature/login:
cat .git/HEAD
# ref: refs/heads/feature/login
```

### Schritt 2: Git ermittelt den Ziel-Commit

Git liest den Hash aus `.git/refs/heads/feature/login` und weiß jetzt, welchen Zustand des Projekts es herstellen muss.

### Schritt 3: Git aktualisiert das Working Directory

Git vergleicht den aktuellen Commit mit dem Ziel-Commit und:

- **Löscht Dateien**, die im Ziel-Commit nicht existieren
- **Erstellt Dateien**, die im Ziel-Commit neu sind
- **Aktualisiert Dateien**, die sich unterscheiden

### Schritt 4: Git aktualisiert den Index (Staging Area)

Der Index wird aktualisiert, um den Zustand des Ziel-Commits widerzuspiegeln.

### Visualisierung des gesamten Prozesses

```mermaid
flowchart TB
    subgraph vorher["Vor dem Wechsel"]
        HEAD_v["HEAD\nref: refs/heads/main"]
        main_v["main\na1b2c3d4"]
        feature_v["feature/login\nb2c3d4e5"]
        WD_v["Working Directory\nZustand von main"]
    end
    
    subgraph nachher["Nach git switch feature/login"]
        HEAD_n["HEAD\nref: refs/heads/feature/login"]
        main_n["main\na1b2c3d4"]
        feature_n["feature/login\nb2c3d4e5"]
        WD_n["Working Directory\nZustand von feature/login"]
    end
    
    vorher --> |"git switch feature/login"| nachher
    
    style HEAD_v fill:#fff3e0,color:#000000
    style HEAD_n fill:#c8e6c9,color:#000000
    style WD_v fill:#e3f2fd,color:#000000
    style WD_n fill:#e8f5e9,color:#000000
```

### Warum Branch-Wechsel manchmal fehlschlägt

Git verhindert den Branch-Wechsel, wenn du **uncommittete Änderungen** hast, die beim Wechsel überschrieben würden:

```bash
git switch feature/login
# error: Your local changes to the following files would be overwritten by checkout:
#     src/Login.php
# Please commit your changes or stash them before you switch branches.
```

Dieses Verhalten schützt dich davor, Arbeit zu verlieren.

---

## Was passiert beim Erstellen eines neuen Commits?

Wenn du `git commit` ausführst, passiert Folgendes mit den Referenzen:

1. **Git erstellt ein neues Commit-Objekt** mit:
   - Tree-Objekt (Snapshot aller Dateien)
   - Parent-Verweis (der vorherige Commit)
   - Autor, Committer, Nachricht, Zeitstempel

2. **Git ermittelt den aktuellen Branch** durch Lesen von HEAD

3. **Git aktualisiert die Branch-Referenz** – die Datei unter `refs/heads/` wird mit dem neuen Commit-Hash überschrieben

```mermaid
flowchart TB
    subgraph vorher["Vor dem Commit"]
        H1["HEAD\nref: refs/heads/main"]
        M1["main\na1b2c3d4"]
        C1["Commit\na1b2c3d4"]
    end
    
    subgraph nachher["Nach dem Commit"]
        H2["HEAD\nref: refs/heads/main"]
        M2["main\nx9y8z7w6"]
        C2["Neuer Commit\nx9y8z7w6"]
        C2_parent["Alter Commit\na1b2c3d4"]
        C2 -->|parent| C2_parent
    end
    
    vorher -->|"git commit"| nachher
    
    style C2 fill:#c8e6c9,color:#000000
    style M2 fill:#c8e6c9,color:#000000
```

**Wichtig:** HEAD selbst ändert sich nicht – es zeigt weiterhin auf `refs/heads/main`. Aber die Datei `refs/heads/main` enthält jetzt einen **neuen Hash**.

---

## Alles zusammen in PhpStorm 🛠️

PhpStorm macht die Arbeit mit Branches komfortabel, aber es hilft zu wissen, was dahinter passiert:

### Branches anzeigen und verstehen

1. Klicke unten rechts in der Statusleiste auf den **Branch-Namen** (z.B. „main")
2. Du siehst eine Liste aller lokalen und Remote-Branches
3. Jeder Eintrag entspricht einer Datei in `.git/refs/`

### Branch erstellen in PhpStorm

1. **Rechtsklick auf einen Commit** im Git-Log → *New Branch...*
2. **Oder:** Branch-Menü → *+ New Branch*
3. PhpStorm erstellt die entsprechende Datei in `.git/refs/heads/`

### HEAD und aktuellen Branch sehen

- Der **aktuelle Branch** ist in der Statusleiste unten rechts sichtbar
- Im **Git-Log** (Alt+9 → Log-Tab) ist der aktuelle Branch fett markiert und HEAD wird angezeigt
- Bei einem „Detached HEAD"-Zustand zeigt PhpStorm eine Warnung

### Refs direkt untersuchen

Du kannst das Terminal in PhpStorm (Alt+F12) nutzen, um die Ref-Dateien direkt zu untersuchen:

```bash
# In PhpStorms Terminal:
cat .git/HEAD
cat .git/refs/heads/main
git show-ref --heads
```

---

## Zusammenfassung der wichtigsten Konzepte

| Konzept | Was es ist | Wo gespeichert | Verhalten |
|---------|-----------|----------------|-----------|
| **Referenz (ref)** | Lesbarer Name für einen Commit-Hash | `.git/refs/` | Basis für Branches und Tags |
| **Branch** | Beweglicher Zeiger auf einen Commit | `.git/refs/heads/<name>` | Wandert mit bei neuen Commits |
| **Remote-Tracking-Branch** | Abbild eines Remote-Branches | `.git/refs/remotes/<remote>/<name>` | Wird bei fetch/pull aktualisiert |
| **Tag** | Fester Zeiger auf einen Commit | `.git/refs/tags/<name>` | Bleibt immer am selben Commit |
| **HEAD** | Zeiger auf aktuellen Branch | `.git/HEAD` | Bestimmt „wo bin ich jetzt" |
| **Detached HEAD** | HEAD zeigt direkt auf Commit | `.git/HEAD` (enthält Hash) | Gefährlich – neue Commits können verloren gehen |

---

## Praktische Übung 📝

Probiere folgende Befehle in einem Test-Repository aus, um das Gelernte zu festigen:

```bash
# 1. Untersuche dein aktuelles Repository
cat .git/HEAD
ls -la .git/refs/heads/
cat .git/refs/heads/main

# 2. Erstelle einen neuen Branch und beobachte die Änderungen
git branch test-branch
ls -la .git/refs/heads/
cat .git/refs/heads/test-branch

# 3. Wechsle den Branch und beobachte HEAD
git switch test-branch
cat .git/HEAD

# 4. Mache einen Commit und beobachte die Branch-Referenz
echo "test" > test.txt
git add test.txt
git commit -m "Test commit"
cat .git/refs/heads/test-branch  # Neuer Hash!

# 5. Aufräumen
git switch main
git branch -d test-branch
```

Wenn du diese Übung durchgeführt hast, wirst du Gits Referenz-System nie wieder als mysteriös empfinden! 🎯

# Das Reflog – deine Lebensversicherung bei Git-Unfällen 🛟

Das Reflog (Reference Log) ist eines der mächtigsten, aber am wenigsten bekannten Features von Git. Es ist buchstäblich dein Sicherheitsnetz, das dich vor fast jedem Git-Unfall retten kann – selbst wenn du denkst, du hättest Commits unwiederbringlich verloren.

---

## Was genau ist das Reflog?

Das Reflog ist ein **lokales Protokoll aller Bewegungen von Referenzen** (insbesondere HEAD) in deinem Repository. Jedes Mal, wenn sich HEAD bewegt – sei es durch einen Commit, Checkout, Reset, Rebase, Merge oder irgendeinen anderen Befehl – wird ein Eintrag im Reflog erstellt.

### Der entscheidende Unterschied zur normalen Historie

Die normale Git-Historie (die du mit `git log` siehst) zeigt dir nur die Commits, die von deinem aktuellen Branch aus erreichbar sind. Das Reflog hingegen protokolliert **jede Zustandsänderung**, unabhängig davon, ob die entsprechenden Commits noch von einem Branch referenziert werden oder nicht.

Stell dir das Reflog wie eine **Zeitmaschine** vor: Es merkt sich nicht nur, wo du jetzt bist, sondern auch jeden Ort, an dem du jemals warst – selbst wenn der Weg dorthin inzwischen „verschwunden" zu sein scheint.

### Was wird im Reflog gespeichert?

Jeder Reflog-Eintrag enthält folgende Informationen:

- **SHA-1-Hash** des Commits, auf den HEAD zu diesem Zeitpunkt zeigte
- **Aktion**, die die Bewegung ausgelöst hat (commit, checkout, reset, rebase, merge, etc.)
- **Zeitstempel** der Aktion
- **Nachricht** mit zusätzlichen Details (z.B. Commit-Message oder Branch-Name)

---

## Warum ist das Reflog deine „Lebensversicherung"? 🆘

In Git gibt es viele Situationen, in denen du denkst, du hättest Daten verloren:

1. **Harter Reset:** Du führst `git reset --hard HEAD~5` aus und merkst, dass du die falschen Commits gelöscht hast
2. **Fehlgeschlagener Rebase:** Ein Interactive Rebase geht schief und deine Commits scheinen verschwunden
3. **Branch gelöscht:** Du löschst versehentlich einen Branch, der noch nicht gemergt war
4. **Falscher Checkout:** Du wechselst den Branch und hast vergessen, Änderungen zu committen
5. **Amend-Unfall:** Du machst `git commit --amend` und überschreibst versehentlich einen wichtigen Commit

In all diesen Fällen gilt: **Die Commits sind nicht wirklich weg!** Sie sind nur nicht mehr von einem Branch aus erreichbar. Das Reflog hat sie jedoch noch protokolliert und kennt ihre SHA-1-Hashes.

### Die 90-Tage-Regel

Git behält Reflog-Einträge standardmäßig **90 Tage** lang (für erreichbare Commits) bzw. **30 Tage** (für nicht mehr erreichbare Commits). Erst danach werden sie bei einer Garbage Collection (`git gc`) endgültig entfernt. Du hast also ein großzügiges Zeitfenster, um Fehler zu korrigieren.

---

## Das Reflog in der Praxis – Kommandozeile

### Grundlegender Befehl

```bash
git reflog
```

Dieser Befehl zeigt dir die letzten Bewegungen von HEAD. Die Ausgabe sieht etwa so aus:

```
a1b2c3d (HEAD -> feature/login) HEAD@{0}: commit: Implementiere Login-Validierung
e4f5g6h HEAD@{1}: commit: Füge Login-Formular hinzu
i7j8k9l HEAD@{2}: checkout: moving from main to feature/login
m0n1o2p (main) HEAD@{3}: reset: moving to HEAD~3
q3r4s5t HEAD@{4}: commit: Wichtiger Commit den ich verloren glaubte
u6v7w8x HEAD@{5}: commit: Noch ein wichtiger Commit
y9z0a1b HEAD@{6}: commit: Der erste wichtige Commit
```

### Die Reflog-Syntax verstehen

Die Notation `HEAD@{n}` ist eine spezielle Referenz-Syntax:

- `HEAD@{0}` – der aktuelle Zustand von HEAD
- `HEAD@{1}` – der vorherige Zustand von HEAD
- `HEAD@{2}` – der Zustand davor
- usw.

Du kannst diese Notation auch mit Zeitangaben verwenden:

```bash
git reflog HEAD@{1.hour.ago}
git reflog HEAD@{yesterday}
git reflog HEAD@{2024-01-15}
```

### Detaillierte Reflog-Ansicht

Für mehr Details kannst du Optionen hinzufügen:

```bash
# Mit vollständigen Commit-Informationen
git reflog show --all

# Mit Datum und Uhrzeit
git reflog --date=iso

# Nur für einen bestimmten Branch
git reflog show feature/login
```

---

## Praktische Rettungsszenarien mit dem Reflog

### Szenario 1: Commits nach hartem Reset wiederherstellen

Du hast versehentlich einen harten Reset durchgeführt und wichtige Commits „verloren":

```bash
# Der fatale Befehl, der alles kaputt gemacht hat
git reset --hard HEAD~5

# Panik! Aber Ruhe bewahren – Reflog anschauen
git reflog
```

Die Ausgabe zeigt dir:

```
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~5
f8e9d0c HEAD@{1}: commit: Letzter wichtiger Commit
b2c3d4e HEAD@{2}: commit: Vorletzter wichtiger Commit
...
```

Jetzt kannst du zu dem Commit vor dem Reset zurückkehren:

```bash
# Entweder mit der Reflog-Referenz
git reset --hard HEAD@{1}

# Oder direkt mit dem SHA-Hash
git reset --hard f8e9d0c
```

### Szenario 2: Gelöschten Branch wiederherstellen

Du hast einen Branch gelöscht und merkst zu spät, dass er wichtige Arbeit enthielt:

```bash
# Der Fehler
git branch -D feature/wichtig

# Reflog zeigt den letzten Commit des Branches
git reflog | grep "feature/wichtig"
# Ausgabe: a1b2c3d HEAD@{5}: checkout: moving from feature/wichtig to main

# Branch wiederherstellen
git checkout -b feature/wichtig a1b2c3d
```

### Szenario 3: Nach fehlgeschlagenem Rebase zurück zum Ursprung

Ein Interactive Rebase ist schiefgegangen:

```bash
# Du hast das hier gemacht
git rebase -i HEAD~10
# Und dabei alles vermasselt...

# Reflog zeigt den Zustand vor dem Rebase
git reflog
# Ausgabe: 
# x1y2z3w (HEAD -> feature) HEAD@{0}: rebase (finish): ...
# ...
# a1b2c3d HEAD@{7}: rebase (start): checkout HEAD~10
# f8e9d0c HEAD@{8}: commit: Mein letzter sauberer Commit

# Zurück zum Zustand vor dem Rebase
git reset --hard HEAD@{8}
```

### Szenario 4: Versehentlich überschriebenen Commit nach Amend wiederherstellen

Du hast `git commit --amend` benutzt und dabei den ursprünglichen Commit überschrieben:

```bash
git reflog
# Ausgabe:
# a1b2c3d (HEAD -> main) HEAD@{0}: commit (amend): Neue Message
# f8e9d0c HEAD@{1}: commit: Ursprüngliche Message die ich behalten wollte

# Der ursprüngliche Commit existiert noch und kann wiederhergestellt werden
git cherry-pick f8e9d0c
# Oder wenn du komplett zurück willst:
git reset --hard f8e9d0c
```

---

## Das Reflog in PhpStorm nutzen 🖥️

PhpStorm bietet eine grafische Oberfläche für das Reflog, die besonders für visuelle Lerntypen hilfreich ist.

### Methode 1: Über das Git-Log-Fenster

1. Öffne das **Git-Tool-Fenster** (unten in PhpStorm) oder über *View → Tool Windows → Git*

2. Im **Log-Tab** siehst du normalerweise die Commit-Historie. Klicke auf das **Uhren-Symbol** (🕐) oder suche nach der Option *„Show Reflog"* in den Filter-Optionen

3. Alternativ kannst du im Suchfeld des Log-Fensters nach spezifischen Commits suchen, wenn du deren SHA-Hash aus dem Terminal-Reflog kennst

### Methode 2: Über das Terminal in PhpStorm

1. Öffne das **Terminal** in PhpStorm (*View → Tool Windows → Terminal* oder `Alt+F12`)

2. Führe dort `git reflog` aus – du bekommst die gleiche Ausgabe wie im externen Terminal

3. **Vorteil:** Du kannst SHA-Hashes direkt kopieren und in PhpStorms Git-Funktionen verwenden

### Methode 3: „Reset Current Branch to Here" nutzen

Wenn du im Git-Log einen bestimmten Commit gefunden hast (auch einen, der nur noch im Reflog existiert), kannst du ihn wiederherstellen:

1. **Rechtsklick** auf den Commit im Log-Fenster
2. Wähle *„Reset Current Branch to Here..."*
3. Wähle den Reset-Modus:
   - **Soft:** Behält alle Änderungen staged
   - **Mixed:** Behält Änderungen, aber unstaged
   - **Hard:** Verwirft alle Änderungen (Vorsicht!)

### Methode 4: Cherry-Pick für einzelne Commits

Wenn du nur einen bestimmten „verlorenen" Commit wiederhaben möchtest:

1. Finde den SHA-Hash im Reflog (Terminal: `git reflog`)
2. In PhpStorm: *Git → Uncommitted Changes → Cherry-Pick* (oder über das Log-Fenster)
3. Gib den SHA-Hash ein oder suche nach dem Commit

### Methode 5: Branch aus Reflog-Commit erstellen

1. Finde den SHA-Hash des gewünschten Commits
2. *Git → Branches → New Branch*
3. Im Dialog kannst du bei *„Starting point"* den SHA-Hash eingeben
4. Damit erstellst du einen neuen Branch, der auf den „verlorenen" Commit zeigt

---

## Das Reflog für verschiedene Referenzen

Das Reflog existiert nicht nur für HEAD, sondern für alle Referenzen in deinem Repository:

```bash
# Reflog für HEAD (Standard)
git reflog show HEAD

# Reflog für einen bestimmten Branch
git reflog show main
git reflog show feature/login

# Reflog für alle Referenzen
git reflog show --all
```

Dies ist besonders nützlich, wenn du wissen möchtest, wie sich ein bestimmter Branch im Laufe der Zeit entwickelt hat, auch über Resets und Force-Pushes hinweg.

---

## Wichtige Einschränkungen des Reflogs ⚠️

Trotz seiner Mächtigkeit hat das Reflog einige Grenzen, die du kennen solltest:

### Das Reflog ist lokal

Das Reflog wird **nicht** mit Remote-Repositories synchronisiert. Es existiert nur auf deinem lokalen Rechner. Das bedeutet:

- Wenn du dein Repository neu klonst, ist das Reflog leer
- Teammitglieder haben jeweils ihr eigenes, unabhängiges Reflog
- Du kannst das Reflog nicht nutzen, um Commits wiederherzustellen, die du nie lokal hattest

### Zeitliche Begrenzung

Reflog-Einträge werden nach einer gewissen Zeit gelöscht:

```bash
# Standard-Einstellungen anzeigen
git config gc.reflogExpire          # Standard: 90 Tage
git config gc.reflogExpireUnreachable   # Standard: 30 Tage

# Falls gewünscht: Längere Aufbewahrung konfigurieren
git config gc.reflogExpire "180 days"
git config gc.reflogExpireUnreachable "90 days"
```

### Garbage Collection beachten

Wenn `git gc` (Garbage Collection) läuft, können alte, nicht mehr referenzierte Commits tatsächlich gelöscht werden – aber nur, wenn sie auch aus dem Reflog abgelaufen sind:

```bash
# Manuelle Garbage Collection (normalerweise nicht nötig)
git gc

# Aggressive GC (kann alte Commits entfernen!)
git gc --aggressive --prune=now
```

---

## Fortgeschrittene Reflog-Techniken

### Commits in einem Zeitraum finden

```bash
# Alle Reflog-Einträge der letzten 2 Stunden
git reflog --since="2 hours ago"

# Reflog-Einträge zwischen zwei Zeitpunkten
git reflog --since="2024-01-01" --until="2024-01-15"
```

### Reflog mit Diff kombinieren

Du kannst dir anschauen, was sich zwischen zwei Reflog-Zuständen geändert hat:

```bash
# Diff zwischen aktuellem Zustand und Zustand vor 5 Aktionen
git diff HEAD@{5} HEAD@{0}

# Welche Dateien haben sich geändert?
git diff --name-only HEAD@{5} HEAD@{0}
```

### Reflog-Einträge als Grundlage für Branches

```bash
# Branch erstellen, der auf einen Reflog-Zustand zeigt
git branch recovery-branch HEAD@{10}

# Direkt zu einem Reflog-Zustand wechseln
git checkout HEAD@{10}  # Achtung: Detached HEAD!
```

---

## Zusammenfassung – Dein Reflog-Notfall-Spickzettel 📋

| Situation | Lösung |
|-----------|--------|
| Commits nach `reset --hard` verloren | `git reflog` → SHA finden → `git reset --hard <SHA>` |
| Branch versehentlich gelöscht | `git reflog` → letzten Commit des Branches finden → `git checkout -b <branch-name> <SHA>` |
| Rebase schiefgegangen | `git reflog` → Zustand vor Rebase finden → `git reset --hard HEAD@{n}` |
| Commit nach Amend überschrieben | `git reflog` → ursprünglichen Commit finden → `git cherry-pick <SHA>` |
| Wissen, was man vor X Stunden gemacht hat | `git reflog --since="X hours ago"` |

**Das Wichtigste zum Schluss:** Wenn etwas schiefgeht, **keine Panik!** Solange du nicht `git gc --prune=now` ausführst oder das Repository löschst, sind deine Commits mit sehr hoher Wahrscheinlichkeit noch da. Das Reflog ist dein Freund – lerne es kennen, bevor du es brauchst! 🛟