# Git für Fortgeschrittene

Du beherrschst die [Grundlagen von Git](https://li.fabula.vision/git) und möchtest jetzt verstehen, **wie Git wirklich funktioniert** – nicht nur, welche Befehle du tippst, sondern was unter der Haube passiert.

# 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! 🛟

# Kapitel 2: Fortgeschrittenes Branching – Strategien für professionelle Entwicklung

**Einleitung:** Im Einsteigerkurs hast du gelernt, Branches zu erstellen und zu mergen. In der professionellen Entwicklung reicht das nicht – du brauchst eine durchdachte Branching-Strategie, die zu deinem Projekt und Team passt. Git Flow, GitHub Flow, Trunk-Based Development – jede Strategie hat ihre Vor- und Nachteile. Außerdem wirst du lernen, wie du mit Long-Running Branches umgehst, Release-Branches verwaltest und Hotfixes in mehrere Branches gleichzeitig einpflegst. Dieses Kapitel verwandelt dich vom Branch-Nutzer zum Branch-Strategen.

# Branching-Strategien im Vergleich: Git Flow, GitHub Flow und Trunk-Based Development

Die Wahl der richtigen Branching-Strategie ist eine der wichtigsten architektonischen Entscheidungen für dein Projekt. Sie beeinflusst, wie dein Team zusammenarbeitet, wie schnell ihr Features ausliefern könnt und wie stabil eure Releases sind. Lass mich dir die drei populärsten Strategien im Detail vorstellen.

---

## 🌳 Git Flow – der strukturierte Klassiker

**Git Flow** wurde 2010 von Vincent Driessen vorgestellt und war lange Zeit *der* Standard für professionelle Softwareentwicklung. Es ist eine ausgeklügelte, aber komplexe Strategie mit klar definierten Branch-Typen und strengen Regeln.

### Die Branch-Struktur

```mermaid
gitGraph
    commit id: "Initial"
    branch develop
    checkout develop
    commit id: "Setup"
    branch feature/login
    checkout feature/login
    commit id: "Login UI"
    commit id: "Login Logic"
    checkout develop
    merge feature/login id: "Merge Login"
    branch release/1.0
    checkout release/1.0
    commit id: "Bugfix"
    commit id: "Version bump"
    checkout main
    merge release/1.0 id: "Release 1.0" tag: "v1.0"
    checkout develop
    merge release/1.0 id: "Back-merge"
```

Git Flow definiert **fünf Branch-Typen** mit jeweils spezifischen Aufgaben:

1. **`main`** (früher `master`)
   
   Der heilige Gral deines Repositories. Dieser Branch enthält *ausschließlich* produktionsreifen Code. Jeder Commit auf `main` repräsentiert eine Release-Version und wird typischerweise mit einem Tag versehen (z.B. `v1.0.0`, `v1.1.0`). Direktes Committen auf `main` ist streng verboten – Änderungen kommen nur durch Merges von Release- oder Hotfix-Branches.

2. **`develop`**
   
   Der Integrations-Branch für alle neuen Entwicklungen. Hier fließen alle abgeschlossenen Features zusammen. `develop` repräsentiert den aktuellen Entwicklungsstand und ist die Basis für neue Feature-Branches. Man könnte sagen: `main` ist „was der Kunde sieht", `develop` ist „was als nächstes kommt".

3. **`feature/*`** (z.B. `feature/user-authentication`, `feature/shopping-cart`)
   
   Für jedes neue Feature wird ein eigener Branch von `develop` abgezweigt. Hier findet die eigentliche Entwicklungsarbeit statt. Feature-Branches können beliebig viele Commits enthalten und existieren so lange, bis das Feature fertig ist. Nach Abschluss wird der Branch zurück in `develop` gemergt und gelöscht.

4. **`release/*`** (z.B. `release/1.2.0`)
   
   Wenn `develop` genügend Features für eine neue Version angesammelt hat, wird ein Release-Branch abgezweigt. Ab diesem Zeitpunkt kommen *keine neuen Features* mehr hinzu – nur noch Bugfixes, Dokumentation und Release-Vorbereitungen (Versionsnummern aktualisieren, Changelog schreiben). Nach Fertigstellung wird der Release-Branch sowohl in `main` als auch zurück in `develop` gemergt.

5. **`hotfix/*`** (z.B. `hotfix/1.0.1-security-patch`)
   
   Für kritische Bugs in der Produktion, die nicht auf den nächsten regulären Release warten können. Hotfix-Branches werden direkt von `main` abgezweigt, enthalten nur die minimale Änderung zur Fehlerbehebung und werden nach Fertigstellung sowohl in `main` (mit neuem Tag) als auch in `develop` gemergt.

### Der typische Workflow in Git Flow

Ein neues Feature durchläuft folgenden Weg:

```
develop → feature/xyz → develop → release/1.0 → main + develop
                                       ↑
                                  (Bugfixes)
```

Und ein Hotfix:

```
main → hotfix/1.0.1 → main + develop
```

### Vor- und Nachteile von Git Flow

| ✅ Vorteile | ❌ Nachteile |
|-------------|--------------|
| Klare Trennung zwischen Entwicklung und Produktion | Hohe Komplexität durch viele Branch-Typen |
| Parallele Arbeit an Release und neuen Features möglich | Lange Feature-Branches führen zu schwierigen Merges |
| Gut geeignet für versionierte Software mit Releases | Langsamerer Entwicklungszyklus |
| Stabile `main`-Branch garantiert | Erfordert Disziplin und Tooling |
| Hotfixes können unabhängig eingespielt werden | Overkill für kleine Teams oder Web-Apps |

### Wann Git Flow verwenden?

Git Flow eignet sich besonders für:

- **Versionierte Software** mit klaren Release-Zyklen (z.B. Desktop-Apps, Mobile Apps, Libraries)
- **Projekte mit Support für mehrere Versionen** gleichzeitig
- **Größere Teams** (5+ Entwickler) mit spezialisierten Rollen
- **Regulierte Umgebungen**, die Nachvollziehbarkeit und Audits erfordern
- **Software mit längeren Release-Zyklen** (Wochen bis Monate)

---

## 🚀 GitHub Flow – schlank und kontinuierlich

**GitHub Flow** wurde von GitHub selbst als Reaktion auf die Komplexität von Git Flow entwickelt. Es ist radikal einfacher: Es gibt nur `main` und Feature-Branches. Die Philosophie ist „ship early, ship often" – kontinuierliche Auslieferung kleiner Änderungen.

### Die Branch-Struktur

```mermaid
gitGraph
    commit id: "Initial"
    branch feature/login
    checkout feature/login
    commit id: "Login UI"
    commit id: "Login Tests"
    checkout main
    merge feature/login id: "PR #1" tag: "deployed"
    branch feature/dashboard
    checkout feature/dashboard
    commit id: "Dashboard"
    checkout main
    merge feature/dashboard id: "PR #2" tag: "deployed"
    branch bugfix/header
    checkout bugfix/header
    commit id: "Fix Header"
    checkout main
    merge bugfix/header id: "PR #3" tag: "deployed"
```

### Die sechs Regeln von GitHub Flow

1. **`main` ist immer deploybar**
   
   Der `main`-Branch muss zu *jedem Zeitpunkt* in Produktion deployt werden können. Das bedeutet: Nur getesteter, funktionierender Code kommt hinein.

2. **Für jede Änderung einen Branch erstellen**
   
   Egal ob Feature, Bugfix oder Experiment – erstelle einen beschreibend benannten Branch von `main` (z.B. `add-user-profile`, `fix-login-timeout`, `experiment/new-checkout`).

3. **Regelmäßig committen und pushen**
   
   Kleine, häufige Commits mit klaren Nachrichten. Push regelmäßig zu GitHub, damit andere den Fortschritt sehen und du ein Backup hast.

4. **Pull Request erstellen, wenn bereit für Review**
   
   Sobald deine Änderung bereit für Feedback ist (oder du Diskussion brauchst), öffne einen Pull Request. Dies ist der zentrale Ort für Code Review und Diskussion.

5. **Nach Review und Approval: Merge in `main`**
   
   Sobald der PR approved ist und alle Tests grün sind, wird er in `main` gemergt. Bei GitHub Flow gibt es keine Zwischenstationen.

6. **Sofort nach dem Merge deployen**
   
   Der Merge in `main` triggert (idealerweise automatisch) ein Deployment. Das bedeutet, dass jeder Merge innerhalb von Minuten in Produktion ist.

### Der typische Workflow in GitHub Flow

```
main → feature-branch → Pull Request → Code Review → main → Deploy
           ↑                               ↓
      (entwickeln)              (automatische Tests)
```

### Vor- und Nachteile von GitHub Flow

| ✅ Vorteile | ❌ Nachteile |
|-------------|--------------|
| Extrem einfach zu verstehen und umzusetzen | Keine native Unterstützung für versionierte Releases |
| Fördert kontinuierliche Integration | Erfordert sehr gute Testabdeckung und CI/CD |
| Schnelle Iteration und Feedback-Zyklen | Hotfixes sind „normal" – keine spezielle Behandlung |
| Pull Requests als zentrale Review-Plattform | Weniger geeignet, wenn mehrere Versionen unterstützt werden müssen |
| Ideal für Web-Applikationen und SaaS | Kann bei großen Features unübersichtlich werden |

### Wann GitHub Flow verwenden?

GitHub Flow eignet sich besonders für:

- **Web-Applikationen und SaaS**, die kontinuierlich deployt werden
- **Kleine bis mittlere Teams** (1–10 Entwickler)
- **Projekte mit guter CI/CD-Pipeline** und automatisierten Tests
- **Agile Teams** mit kurzen Sprints und häufigen Releases
- **Startups und schnell iterierende Produkte**

---

## 🎯 Trunk-Based Development – der radikale Ansatz

**Trunk-Based Development (TBD)** geht noch einen Schritt weiter als GitHub Flow. Die Idee: Alle Entwickler committen direkt in einen einzigen Branch (den „Trunk", typischerweise `main`). Feature-Branches existieren entweder gar nicht oder sind extrem kurzlebig (maximal 1–2 Tage).

### Die Branch-Struktur

```mermaid
gitGraph
    commit id: "Feature A - Part 1"
    commit id: "Feature A - Part 2"
    commit id: "Bugfix"
    commit id: "Feature B" tag: "deployed"
    commit id: "Refactoring"
    commit id: "Feature A - Part 3"
    commit id: "Feature A complete" tag: "deployed"
```

### Die Kernprinzipien

1. **Ein einziger Branch für alle**
   
   Es gibt nur `main` (den „Trunk"). Alle Entwickler integrieren ihre Änderungen hier. Lange lebende Feature-Branches sind *verboten*.

2. **Sehr kleine, sehr häufige Commits**
   
   Statt eines großen Commits am Ende eines Features gibt es viele kleine Commits während der Entwicklung. Jeder Commit muss den Build grün halten.

3. **Feature Flags für unfertige Features**
   
   Wie bringt man Code für ein halbfertiges Feature in `main`, ohne dass Nutzer es sehen? Durch **Feature Flags** (auch Feature Toggles genannt):

   ```php
   if ($featureFlags->isEnabled('new-checkout-process')) {
       return $this->newCheckoutProcess($cart);
   } else {
       return $this->legacyCheckout($cart);
   }
   ```

   Das Feature wird erst „eingeschaltet", wenn es fertig ist – obwohl der Code schon lange in Produktion ist.

4. **Kurzlebige Feature-Branches (optional)**
   
   Manche Teams erlauben Feature-Branches, aber mit strikter Regel: Sie dürfen maximal 1–2 Tage existieren und müssen dann gemergt werden. Das erzwingt kleine, inkrementelle Änderungen.

5. **Exzellente CI/CD ist Pflicht**
   
   Da jeder Commit potenziell in Produktion geht, muss die Test-Pipeline *schnell* und *zuverlässig* sein. Typischerweise < 10 Minuten für den kompletten Build.

### Branch by Abstraction

Für größere Refactorings, die nicht in 1–2 Tagen erledigt sind, nutzt TBD das Pattern „**Branch by Abstraction**":

1. Erstelle eine Abstraktionsschicht (Interface) vor dem zu ändernden Code
2. Implementiere die neue Version hinter dem Interface
3. Schalte schrittweise um (Feature Flag oder schrittweise Migration)
4. Entferne die alte Implementierung, sobald die neue stabil ist

So bleibt der Code immer lauffähig, obwohl du parallel zwei Implementierungen hast.

### Vor- und Nachteile von Trunk-Based Development

| ✅ Vorteile | ❌ Nachteile |
|-------------|--------------|
| Keine Merge-Hölle durch lang lebende Branches | Erfordert erfahrene, disziplinierte Entwickler |
| Maximale Kontinuierliche Integration | Feature Flags erhöhen Code-Komplexität |
| Sehr schnelles Feedback | Sehr gute Testabdeckung ist Voraussetzung |
| Fördert kleine, fokussierte Änderungen | Kann für Junior-Entwickler überfordernd sein |
| Von Google, Facebook und anderen Tech-Giganten verwendet | Weniger Review-Möglichkeiten vor dem Merge |

### Wann Trunk-Based Development verwenden?

Trunk-Based Development eignet sich besonders für:

- **Erfahrene, hochperformante Teams** mit starker Ingenieurskultur
- **Unternehmen mit exzellenter CI/CD-Infrastruktur**
- **Projekte, die mehrmals täglich deployen** wollen
- **Teams, die Pair Programming praktizieren** (ersetzt Code Review)
- **Microservices-Architekturen** mit kleinen, fokussierten Repositories

---

## 📊 Der große Vergleich

| Aspekt | Git Flow | GitHub Flow | Trunk-Based |
|--------|----------|-------------|-------------|
| **Komplexität** | Hoch | Niedrig | Mittel* |
| **Branch-Typen** | 5 (main, develop, feature, release, hotfix) | 2 (main, feature) | 1 (main) |
| **Lebensdauer Feature-Branch** | Tage bis Wochen | Stunden bis Tage | Keine oder < 2 Tage |
| **Release-Modell** | Geplante Versionen | Kontinuierlich | Kontinuierlich |
| **Merge-Frequenz** | Selten, dafür größer | Häufig | Sehr häufig (mehrmals täglich) |
| **CI/CD-Anforderung** | Optional | Empfohlen | Pflicht |
| **Feature Flags nötig?** | Nein | Selten | Ja |
| **Parallele Versionen** | Ja | Nein | Nein |
| **Team-Größe** | Mittel bis groß | Klein bis mittel | Klein bis mittel (erfahren) |
| **Lernkurve** | Steil | Flach | Mittel |

*\*Die Komplexität bei TBD liegt nicht im Branching, sondern in den Praktiken (Feature Flags, kleine Commits, CI/CD).*

---

## 💡 Meine Empfehlung für dein PHP-Webprojekt

Du beschreibst ein **mittelgroßes PHP-Webprojekt mit regelmäßigen Releases**. Lass mich die Faktoren analysieren:

### Analyse deiner Situation

- **„Mittelgroß"** deutet auf ein Team von 2–8 Entwicklern hin
- **„PHP-Webprojekt"** ist typischerweise eine Web-Applikation, die deployt wird (nicht installierte Software)
- **„Regelmäßige Releases"** können unterschiedlich interpretiert werden:
  - Wöchentlich/zweiwöchentlich → GitHub Flow geeignet
  - Monatlich mit festen Versionen → Git Flow könnte passen
  - Kontinuierlich → Trunk-Based möglich

### Meine Empfehlung: **GitHub Flow mit Release-Tags** 🏆

Für dein Szenario empfehle ich **GitHub Flow**, aber mit einer kleinen Erweiterung für die Versionierung:

```mermaid
flowchart LR
    subgraph Entwicklung
        A["Feature-Branch\nerstellen"] --> B["Entwickeln\nund testen"]
        B --> C["Pull Request\nerstellen"]
        C --> D["Code Review"]
        D --> E["Merge in main"]
    end
    
    subgraph Release
        E --> F{"Release\nfällig?"}
        F -->|Ja| G["Tag erstellen\nv1.2.0"]
        F -->|Nein| H["Weiter entwickeln"]
        G --> I["Deployment"]
    end
    
    style G fill:#90EE90,color:#000000
    style I fill:#87CEEB,color:#000000
```

### Warum GitHub Flow für dich?

1. **Einfachheit:** Du und dein Team müsst nicht fünf verschiedene Branch-Typen jonglieren. Die Regeln passen auf einen Bierdeckel.

2. **Web-Applikation:** PHP-Webprojekte werden deployt, nicht installiert. Du brauchst keine parallelen Versionen zu unterstützen – es gibt nur „was gerade live ist".

3. **Pull Requests als Qualitätssicherung:** Jede Änderung durchläuft einen Review-Prozess. Das ist Gold wert für die Code-Qualität.

4. **Flexibilität bei Releases:** Du kannst so oft oder selten releasen, wie du möchtest. Ein Release ist einfach ein Tag auf `main`.

5. **Einfacher Einstieg:** Falls Teammitglieder noch nicht so Git-erfahren sind, ist GitHub Flow viel leichter zu erlernen als Git Flow.

### Wie du „regelmäßige Releases" mit GitHub Flow umsetzt

Anstatt Release-Branches zu nutzen, arbeitest du mit **Tags** und einem **Release-Rhythmus**:

```bash
# Nach dem Merge aller Features für Version 1.2.0
git tag -a v1.2.0 -m "Release 1.2.0: User Dashboard, Performance Improvements"
git push origin v1.2.0
```

In GitHub kannst du dann einen formalen Release erstellen, der auf diesen Tag verweist – mit Changelog, Release Notes und ggf. Assets.

### Dein angepasster Workflow

1. **Für jede Aufgabe:** Erstelle einen Branch von `main`
   
   ```bash
   git checkout main
   git pull
   git checkout -b feature/user-dashboard
   ```

2. **Entwickle und committe** regelmäßig mit aussagekräftigen Nachrichten

3. **Erstelle einen Pull Request** auf GitHub, wenn bereit für Review

4. **Nach Approval:** Merge in `main` (ich empfehle „Squash and Merge" für eine saubere Historie)

5. **Für Releases:** Wenn genügend Features gemergt sind, erstelle einen Tag und deploye:
   
   ```bash
   git checkout main
   git pull
   git tag -a v1.2.0 -m "Release 1.2.0"
   git push origin v1.2.0
   # Deployment triggern (manuell oder automatisch via GitHub Actions)
   ```

6. **Für Hotfixes:** Behandle sie wie normale Features – Branch erstellen, fixen, PR, mergen. Danach ggf. Patch-Version taggen (`v1.2.1`).

### Wann du doch Git Flow in Betracht ziehen solltest

Überdenke meine Empfehlung, wenn *einer* dieser Punkte zutrifft:

- Du musst **mehrere Versionen parallel unterstützen** (z.B. v1.x und v2.x gleichzeitig pflegen)
- Du hast **sehr lange Release-Zyklen** (> 1 Monat) mit umfangreichen QA-Phasen
- Du arbeitest in einer **regulierten Branche** (Medizin, Finanzen) mit strengen Audit-Anforderungen
- Dein Team ist **sehr groß** (> 10 Entwickler) und braucht klare Abgrenzungen

### Wann Trunk-Based Development interessant wäre

- Du möchtest **mehrmals täglich deployen**
- Dein Team ist **sehr erfahren** und praktiziert Pair Programming
- Du hast eine **exzellente CI/CD-Pipeline** mit < 10 Minuten Build-Zeit
- Du bist bereit, in **Feature Flags** zu investieren

---

## 🎓 Zusammenfassung

| Strategie | Motto | Ideal für |
|-----------|-------|-----------|
| **Git Flow** | „Struktur und Kontrolle" | Versionierte Software, große Teams, lange Zyklen |
| **GitHub Flow** | „Ship early, ship often" | Web-Apps, kleine-mittlere Teams, kontinuierliche Delivery |
| **Trunk-Based** | „Immer integriert" | Hochperformante Teams, mehrfach tägliches Deployment |

Für dein mittelgroßes PHP-Webprojekt ist **GitHub Flow** der Sweet Spot: Einfach genug, um schnell produktiv zu sein, aber strukturiert genug für professionelle Zusammenarbeit. Mit Tags und GitHub Releases bekommst du die Versionierung, die du für „regelmäßige Releases" brauchst – ohne die Komplexität von Git Flow.

Starte mit GitHub Flow, und wenn du merkst, dass du mehr Struktur brauchst, kannst du immer noch zu Git Flow wechseln. Der umgekehrte Weg (von komplex zu einfach) ist erfahrungsgemäß schwieriger! 😉

# Long-Running Branches verstehen und synchron halten

## Was sind Long-Running Branches?

**Long-Running Branches** (auch *langlebige* oder *permanente Branches* genannt) sind Branches, die über die gesamte Lebensdauer eines Projekts oder zumindest über längere Zeiträume hinweg bestehen bleiben – im Gegensatz zu **kurzlebigen Feature-Branches**, die nach dem Merge gelöscht werden.

### Die typischen Long-Running Branches

In den meisten professionellen Projekten findest du folgende Struktur:

| Branch | Zweck | Lebensdauer |
|--------|-------|-------------|
| `main` (oder `master`) | Produktionscode – enthält nur stabilen, deploybaren Code | Permanent |
| `develop` | Integrationsbranch für die nächste Version – hier fließen Feature-Branches zusammen | Permanent |
| `release/*` | Vorbereitung einer neuen Version, nur noch Bugfixes erlaubt | Mittelfristig (Wochen) |
| `hotfix/*` | Kritische Fixes für Produktion | Kurzfristig (Tage) |

```mermaid
gitGraph
    commit id: "Initial"
    branch develop
    commit id: "Setup"
    branch feature/login
    commit id: "Login-Form"
    commit id: "Validation"
    checkout develop
    merge feature/login id: "Merge Login"
    branch release/1.0
    commit id: "Version bump"
    commit id: "Bugfix"
    checkout main
    merge release/1.0 id: "Release 1.0" tag: "v1.0.0"
    checkout develop
    merge release/1.0 id: "Sync Release"
```

### Warum ist Synchronisation so wichtig? ⚠️

Stell dir folgendes Szenario vor:

1. Du erstellst am Montag einen Feature-Branch von `main`
2. Du arbeitest zwei Wochen an deinem Feature
3. In der Zwischenzeit wurden 50 Commits in `main` gemergt (von anderen Entwicklern oder anderen Features)
4. Dein Feature-Branch ist jetzt **zwei Wochen und 50 Commits hinter `main`**

**Die Probleme, die entstehen können:**

- **Merge-Konflikte:** Je länger du wartest, desto mehr Konflikte häufen sich an. Ein Merge nach zwei Wochen kann dutzende Konflikte haben, die du alle auf einmal lösen musst.
- **Inkompatibilitäten:** Vielleicht hat jemand eine Funktion geändert, die du verwendest. Dein Code funktioniert nicht mehr mit dem aktuellen Stand.
- **Doppelte Arbeit:** Möglicherweise wurde bereits Code geschrieben, den du gerade selbst implementierst.
- **Veraltete Basis:** Du testest dein Feature gegen eine veraltete Codebasis – es könnte in Produktion anders funktionieren.

**Die Lösung:** Regelmäßige Synchronisation – idealerweise *täglich* oder zumindest bei jedem größeren Meilenstein in `main`.

---

## Die zwei Strategien: Merge vs. Rebase

Es gibt zwei grundlegend verschiedene Ansätze, um Änderungen aus `main` in deinen Feature-Branch zu integrieren. Beide haben ihre Berechtigung, und die Wahl hängt von deinem Workflow und deinen Team-Konventionen ab.

### Strategie 1: Merge (main → Feature-Branch)

Bei einem **Merge** werden die Änderungen aus `main` in deinen Feature-Branch integriert, wobei ein **Merge-Commit** entsteht, der beide Historien verbindet.

```mermaid
gitGraph
    commit id: "A"
    commit id: "B"
    branch feature/shop
    commit id: "F1"
    commit id: "F2"
    checkout main
    commit id: "C"
    commit id: "D"
    checkout feature/shop
    merge main id: "Merge main" type: HIGHLIGHT
    commit id: "F3"
```

**Was passiert hier?**
- Dein Feature-Branch enthält nach dem Merge sowohl deine Commits (F1, F2) als auch die neuen Commits aus `main` (C, D)
- Ein Merge-Commit dokumentiert, *wann* du synchronisiert hast
- Die ursprüngliche Historie bleibt vollständig erhalten

### Strategie 2: Rebase (Feature-Branch auf main)

Bei einem **Rebase** werden deine Feature-Commits „abgelöst" und auf die Spitze von `main` *neu aufgesetzt*. Es entsteht eine **lineare Historie** ohne Merge-Commits.

```mermaid
gitGraph
    commit id: "A"
    commit id: "B"
    commit id: "C"
    commit id: "D"
    branch feature/shop
    commit id: "F1 neu" type: HIGHLIGHT
    commit id: "F2 neu" type: HIGHLIGHT
    commit id: "F3"
```

**Was passiert hier?**
- Deine Commits werden „verschoben" – sie basieren jetzt auf dem aktuellen Stand von `main`
- Es entstehen *technisch gesehen* neue Commits (mit neuen Hashes), auch wenn der Inhalt identisch ist
- Die Historie sieht aus, als hättest du erst jetzt mit deiner Arbeit begonnen

---

## Ausführliche Anleitung: Synchronisation mit Merge

### Szenario

Du arbeitest an einem Feature-Branch `feature/newsletter` und möchtest die neuesten Änderungen aus `main` integrieren.

### Schritt 1: Sicherstellen, dass du einen sauberen Arbeitsstand hast

Bevor du irgendetwas synchronisierst, sollte dein Working Directory „sauber" sein – also keine uncommitteten Änderungen enthalten.

**In PhpStorm prüfen:**
- Schau in der linken unteren Ecke auf den „Git"-Tab
- Unter „Local Changes" sollten keine Dateien stehen

**Falls du unfertige Änderungen hast**, nutze Stash:

```bash
# Terminal-Variante
git stash push -m "WIP: Newsletter-Formular"
```

**In PhpStorm:**
1. Menü: **Git → Uncommitted Changes → Stash Changes...**
2. Gib eine aussagekräftige Nachricht ein
3. Klicke **Create Stash**

### Schritt 2: Den neuesten Stand von `main` holen (Fetch)

Zuerst musst du sicherstellen, dass dein lokales Repository die neuesten Informationen vom Remote hat.

**In PhpStorm:**
1. Klicke auf das blaue **↓ Pfeil-Symbol** in der Toolbar (oder **Ctrl+T** / **Cmd+T**)
2. Oder: Menü **Git → Fetch**

Dies aktualisiert `origin/main` mit dem Stand auf GitHub, ohne deinen lokalen `main`-Branch zu verändern.

### Schritt 3: main in deinen Feature-Branch mergen

**Methode A: Über das Git-Log in PhpStorm** *(empfohlen)*

1. Öffne das **Git-Log**: Klicke unten auf den Tab „Git" und dann auf „Log"
2. Stelle sicher, dass du auf deinem Feature-Branch bist (sichtbar unten rechts in der Statusleiste)
3. Im Log siehst du alle Branches – finde `origin/main`
4. **Rechtsklick auf den neuesten Commit von `origin/main`**
5. Wähle **„Merge 'origin/main' into 'feature/newsletter'"**

```mermaid
flowchart TD
    A["1. Git-Log offnen\n Ansicht: Git - Log"] --> B["2. Branch prufen\n Bist du auf feature/newsletter?"]
    B --> C["3. origin/main finden\n Im Log-Graphen"]
    C --> D["4. Rechtsklick auf\n neuesten Commit"]
    D --> E["5. Merge origin/main\n into feature/newsletter"]
    E --> F{Konflikte?}
    F -->|Ja| G["Konflikte losen\n 3-Way-Merge-Editor"]
    F -->|Nein| H["Fertig!\n Merge-Commit erstellt"]
    G --> H
    
    style A fill:#e1f5fe,color:#000000
    style E fill:#fff3e0,color:#000000
    style H fill:#e8f5e9,color:#000000
```

**Methode B: Über das Branch-Menü**

1. Klicke unten rechts auf den aktuellen Branch-Namen (z.B. `feature/newsletter`)
2. Im Dropdown: Finde **„origin/main"** unter „Remote Branches"
3. Klicke darauf und wähle **„Merge into Current"**

**Methode C: Über das Hauptmenü**

1. Menü: **Git → Merge...**
2. Wähle `origin/main` aus der Liste
3. Klicke **Merge**

### Schritt 4: Konflikte lösen (falls vorhanden)

Wenn es Konflikte gibt, öffnet PhpStorm automatisch ein Dialogfenster „Files Merged with Conflicts":

1. Klicke auf **„Merge..."** neben der konfliktbehafteten Datei
2. Der **3-Way-Merge-Editor** öffnet sich:
   - **Links:** Deine Version (feature/newsletter)
   - **Rechts:** Ihre Version (main)
   - **Mitte:** Das Ergebnis, das du zusammenbaust
3. Nutze die `>>` und `<<` Buttons, um Änderungen zu übernehmen
4. Wenn du fertig bist: **Apply**
5. Nachdem alle Konflikte gelöst sind: Der Merge-Commit wird automatisch erstellt

### Schritt 5: Ergebnis überprüfen

Nach dem Merge solltest du kurz prüfen:

1. **Schau ins Git-Log:** Du siehst jetzt einen Merge-Commit mit zwei Eltern-Commits
2. **Führe deine Tests aus:** Stelle sicher, dass alles noch funktioniert
3. **Pushe deinen Branch:** `Ctrl+Shift+K` oder **Git → Push**

### Terminal-Befehle (zur Referenz)

```bash
# 1. Auf Feature-Branch wechseln (falls nicht schon dort)
git checkout feature/newsletter

# 2. Neueste Daten vom Remote holen
git fetch origin

# 3. main in Feature-Branch mergen
git merge origin/main

# 4. Bei Konflikten: Nach dem manuellen Lösen
git add .
git commit  # Merge-Commit abschließen

# 5. Pushen
git push origin feature/newsletter
```

---

## Ausführliche Anleitung: Synchronisation mit Rebase

### Wichtiger Hinweis vorab ⚠️

Rebase schreibt die Historie um – deine Commits bekommen **neue SHA-Hashes**. Das bedeutet:

- **Wenn du deinen Branch noch nicht gepusht hast:** Kein Problem, rebase frei.
- **Wenn du schon gepusht hast und alleine am Branch arbeitest:** Möglich, aber du brauchst `git push --force-with-lease`.
- **Wenn andere auch an deinem Branch arbeiten:** 🚫 **Kein Rebase!** Du würdest die Arbeit der anderen durcheinanderbringen.

### Szenario

Du arbeitest an `feature/checkout` und möchtest deine Commits auf den aktuellen Stand von `main` neu aufsetzen.

### Schritt 1: Vorbereitung (wie beim Merge)

- Sicherstellen, dass das Working Directory sauber ist
- Unfertige Änderungen ggf. stashen
- `git fetch` ausführen, um `origin/main` zu aktualisieren

### Schritt 2: Rebase in PhpStorm starten

**Methode A: Über das Git-Menü** *(empfohlen für Einsteiger)*

1. Stelle sicher, dass du auf deinem Feature-Branch bist
2. Menü: **Git → Rebase...**
3. Im Dialog „Rebase":
   - „Onto": Wähle `origin/main`
   - Die anderen Optionen kannst du auf Standard lassen
4. Klicke **Rebase**

**Methode B: Über das Branch-Popup**

1. Klicke unten rechts auf deinen Branch-Namen
2. Finde `origin/main` unter „Remote Branches"
3. Klicke darauf und wähle **„Rebase Current onto Selected"**

**Methode C: Über das Git-Log**

1. Öffne das Git-Log
2. Rechtsklick auf den neuesten Commit von `origin/main`
3. Wähle **„Rebase 'feature/checkout' onto 'origin/main'"**

### Schritt 3: Konflikte während des Rebases lösen

Beim Rebase werden deine Commits *einzeln* auf `main` neu angewendet. Das bedeutet: Wenn es Konflikte gibt, musst du sie **für jeden betroffenen Commit einzeln lösen**.

**Der Ablauf bei Konflikten:**

```mermaid
flowchart TD
    A["Rebase starten"] --> B["Commit 1 anwenden"]
    B --> C{Konflikt?}
    C -->|Nein| D["Commit 2 anwenden"]
    C -->|Ja| E["Konflikt losen"]
    E --> F["Git - Rebase - Continue"]
    F --> D
    D --> G{Konflikt?}
    G -->|Nein| H["Commit 3 anwenden"]
    G -->|Ja| I["Konflikt losen"]
    I --> J["Git - Rebase - Continue"]
    J --> H
    H --> K["Rebase abgeschlossen!"]
    
    style A fill:#e1f5fe,color:#000000
    style E fill:#ffcccc,color:#000000
    style I fill:#ffcccc,color:#000000
    style K fill:#e8f5e9,color:#000000
```

**Konkret in PhpStorm:**

1. Bei einem Konflikt zeigt PhpStorm eine Notification: „Rebase stopped due to conflicts"
2. Löse die Konflikte im 3-Way-Merge-Editor (wie beim Merge)
3. Nach dem Lösen: **Git → Rebase → Continue**
4. PhpStorm wendet den nächsten Commit an
5. Wiederhole, bis alle Commits neu angewendet wurden

**Wenn du abbrechen möchtest:**
- **Git → Rebase → Abort** – Stellt den Zustand vor dem Rebase wieder her

### Schritt 4: Force-Push (falls bereits gepusht)

Da Rebase neue Commits erstellt, musst du mit `--force` pushen, wenn der Branch bereits auf GitHub existiert.

**In PhpStorm:**
1. `Ctrl+Shift+K` (oder **Git → Push**)
2. PhpStorm zeigt dir, dass ein Force-Push nötig ist
3. Klicke auf den kleinen Pfeil neben „Push" und wähle **„Force Push"**

**Besser: Verwende `--force-with-lease`:**

```bash
git push --force-with-lease origin feature/checkout
```

Dies ist sicherer als `--force`, weil es prüft, ob jemand anderes in der Zwischenzeit gepusht hat.

**In PhpStorm aktivieren:**
1. **Settings → Version Control → Git**
2. Aktiviere **„Use --force-with-lease for force push"**

### Terminal-Befehle (zur Referenz)

```bash
# 1. Auf Feature-Branch wechseln
git checkout feature/checkout

# 2. Fetch
git fetch origin

# 3. Rebase starten
git rebase origin/main

# 4a. Bei Konflikten: Nach dem Lösen weitermachen
git add .
git rebase --continue

# 4b. Oder abbrechen
git rebase --abort

# 5. Force-Push (nur wenn schon gepusht)
git push --force-with-lease origin feature/checkout
```

---

## Merge vs. Rebase: Direkter Vergleich

| Aspekt | Merge | Rebase |
|--------|-------|--------|
| **Historie** | Bewahrt die komplette, echte Historie mit allen Verzweigungen | Erzeugt eine lineare, „saubere" Historie |
| **Merge-Commits** | Ja, ein Merge-Commit pro Synchronisation | Nein, keine zusätzlichen Commits |
| **Commit-Hashes** | Bleiben unverändert | Ändern sich (neue Commits) |
| **Konfliktlösung** | Alle Konflikte auf einmal | Konflikte pro Commit einzeln |
| **Force-Push nötig?** | Nein | Ja, wenn Branch bereits gepusht |
| **Sicher bei Team-Branches?** | ✅ Ja | ⚠️ Nur mit Vorsicht |
| **Nachvollziehbarkeit** | Sehr gut (wann wurde was integriert?) | Geschichte wird „umgeschrieben" |
| **Komplexität** | Einfacher | Komplexer |

### Visuelle Gegenüberstellung

**Nach mehreren Merge-Synchronisationen:**

```
main:     A───B───C───D───E───F───G
               \       \       \
feature:        X───Y───M───Z───M───W
                       ↑       ↑
                  Merge-Commits
```

**Nach Rebase (vor dem finalen Merge):**

```
main:     A───B───C───D───E───F───G
                                   \
feature:                            X'──Y'──Z'──W'
                                    ↑
                               Neu aufgesetzte Commits
```

---

## Welche Strategie solltest du wählen?

### Wähle **Merge**, wenn...

- 👥 **Mehrere Personen am selben Branch arbeiten** – Rebase würde deren Arbeit zerstören
- 📜 **Du eine vollständige Audit-Trail brauchst** – z.B. in regulierten Umgebungen
- 🆕 **Du noch unsicher mit Git bist** – Merge ist verzeihender
- 🔀 **Dein Team Merge bevorzugt** – Konsistenz ist wichtiger als persönliche Vorliebe

### Wähle **Rebase**, wenn...

- 👤 **Du alleine am Feature-Branch arbeitest**
- ✨ **Du eine saubere, lineare Historie bevorzugst**
- 📖 **Dein Team „Rebase before Merge" als Konvention hat**
- 🧹 **Du deine Commits vor dem PR aufräumen möchtest** (Interactive Rebase)

### Der Kompromiss: „Rebase lokal, Merge für Integration"

Viele Teams nutzen eine **hybride Strategie**:

1. **Während der Feature-Entwicklung:** Rebase nutzen, um den Feature-Branch aktuell zu halten (solange nicht gepusht oder alleine am Branch)
2. **Für den finalen Merge:** Einen normalen Merge (oder Squash-Merge) in `main` machen

```mermaid
flowchart LR
    A["Feature-Branch\n erstellen"] --> B["Entwickeln"]
    B --> C{"Neue Commits\n in main?"}
    C -->|Ja| D["Rebase auf main\n lokal"]
    D --> B
    C -->|Nein| E{"Feature\n fertig?"}
    E -->|Nein| B
    E -->|Ja| F["Pull Request\n erstellen"]
    F --> G["Code Review"]
    G --> H["Merge PR\n in main"]
    
    style D fill:#fff3e0,color:#000000
    style H fill:#e8f5e9,color:#000000
```

---

## Praktische Tipps für die tägliche Arbeit 💡

### Tipp 1: Synchronisiere regelmäßig

Mache es dir zur Gewohnheit, **mindestens einmal täglich** (oder bei jedem PR, der in `main` gemergt wird) zu synchronisieren:

```bash
# Schneller Check am Morgen
git fetch origin
git log HEAD..origin/main --oneline
# Zeigt dir, wie viele Commits du "hinterher" bist
```

In PhpStorm siehst du das im Git-Log: Wenn `origin/main` weiter ist als der Verzweigungspunkt deines Branches, ist es Zeit zu synchronisieren.

### Tipp 2: Nutze PhpStorms „Update Project"

Der schnellste Weg für den täglichen Workflow:

1. `Ctrl+T` (oder **Git → Update Project**)
2. Wähle **Rebase** oder **Merge** (du kannst eine Standardeinstellung setzen)
3. PhpStorm führt Fetch + Merge/Rebase in einem Schritt aus

### Tipp 3: Konfiguriere deine Standard-Strategie

**In PhpStorm:**
- **Settings → Version Control → Git**
- Unter „Update Method": Wähle deine bevorzugte Methode

**Global in Git:**

```bash
# Für Rebase als Standard
git config --global pull.rebase true

# Für Merge als Standard (default)
git config --global pull.rebase false
```

### Tipp 4: Erstelle einen Alias für den Workflow

Wenn du häufig synchronisierst, erstelle einen Git-Alias:

```bash
# In ~/.gitconfig oder via Befehl:
git config --global alias.sync-main '!git fetch origin && git rebase origin/main'

# Nutzung:
git sync-main
```

### Tipp 5: Kommuniziere mit deinem Team

Die wichtigste „Technik" ist eigentlich keine technische: **Sprich mit deinem Team!**

- Einigt euch auf **eine** Strategie (Merge oder Rebase)
- Dokumentiert sie in der `CONTRIBUTING.md`
- Nutzt **Branch Protection Rules**, um die Strategie durchzusetzen

---

## Zusammenfassung 📋

**Long-Running Branches** sind permanente Branches wie `main` und `develop`, die als Integrationspunkte dienen und niemals gelöscht werden.

**Regelmäßige Synchronisation** ist essentiell, um:
- Merge-Konflikte klein zu halten
- Gegen aktuellen Code zu entwickeln
- Integrationsprobleme früh zu erkennen

**Zwei Strategien stehen zur Verfügung:**

1. **Merge** (`git merge origin/main`):
   - Erzeugt Merge-Commits
   - Bewahrt die echte Historie
   - Sicher für Team-Branches

2. **Rebase** (`git rebase origin/main`):
   - Erzeugt lineare Historie
   - Ändert Commit-Hashes (Force-Push nötig)
   - Nur für Branches, an denen du alleine arbeitest

**Die beste Strategie** ist die, auf die sich dein Team einigt und die konsistent angewendet wird.

# Der Hotfix-Workflow: Kritische Bugfixes sicher in alle Branches bringen 🚨

Ein Hotfix ist eine der stressigsten Situationen in der Softwareentwicklung: Ein kritischer Bug ist in der Produktion aufgetaucht und muss *sofort* behoben werden – ohne dabei die laufende Entwicklungsarbeit zu gefährden oder den Fix irgendwo zu „vergessen". In dieser Lektion zeige ich dir, wie du einen professionellen Hotfix-Workflow umsetzt und dabei sicherstellst, dass der Fix in allen relevanten Branches ankommt.

---

## Das Grundproblem verstehen

Stell dir folgendes Szenario vor:

```
main (v1.2.0) ──●──●──●  ← Produktionsversion, hier ist der Bug!
                    \
develop ─────────────●──●──●──●  ← Hier wird an v1.3.0 gearbeitet
                          \
feature/neues-login ───────●──●  ← Feature in Entwicklung
```

Der Bug existiert in `main` (der Produktionsversion), aber auch in `develop` und allen Feature-Branches, die von `main` oder `develop` abgezweigt wurden. Wenn du den Fix nur in `main` einspielst, wird er bei der nächsten Entwicklung wieder überschrieben. Spielst du ihn nur in `develop` ein, dauert es bis zum nächsten Release, bis er in Produktion kommt.

**Die Lösung:** Ein dedizierter Hotfix-Branch, der von `main` abzweigt und nach der Behebung in *alle* relevanten Branches gemergt wird.

---

## Der klassische Hotfix-Workflow (Schritt für Schritt)

### 1. Hotfix-Branch von `main` erstellen

Der Hotfix-Branch wird **immer von `main`** (oder dem Branch, der die Produktion repräsentiert) erstellt – niemals von `develop`! So stellst du sicher, dass du exakt den Code-Stand der Produktion als Basis hast.

**In PhpStorm:**

1. Öffne das **Git-Tool-Window** (`Alt+9` / `Cmd+9`)
2. Stelle sicher, dass du auf `main` bist (Rechtsklick auf `main` → *Checkout*)
3. Führe einen **Pull** durch, um sicherzustellen, dass du den aktuellen Stand hast
4. Klicke auf **New Branch** (oder `Ctrl+Shift+`\` / `Cmd+Shift+`\`)
5. Benenne den Branch nach dem Schema: `hotfix/kurze-beschreibung` oder `hotfix/v1.2.1`

**Auf der Kommandozeile:**

```bash
# Sicherstellen, dass main aktuell ist
git checkout main
git pull origin main

# Hotfix-Branch erstellen
git checkout -b hotfix/kritischer-login-bug
```

**Wichtige Namenskonventionen:**

- `hotfix/beschreibung` – z.B. `hotfix/sql-injection-fix`
- `hotfix/vX.Y.Z` – z.B. `hotfix/v1.2.1` (wenn du semantische Versionierung nutzt)
- `hotfix/issue-123` – wenn du ein Issue-Tracking-System verwendest

---

### 2. Den Bug beheben und committen

Jetzt behebst du den Bug. Dabei gelten einige wichtige Regeln:

**Nur den Bug fixen – nichts anderes!** Ein Hotfix sollte so minimal wie möglich sein. Jede zusätzliche Änderung erhöht das Risiko, neue Probleme einzuführen. Widerstehe der Versuchung, „schnell noch" andere kleine Dinge zu korrigieren.

**Commit-Message mit Kontext:**

```bash
git add src/Auth/LoginController.php
git commit -m "fix: SQL-Injection-Schwachstelle im Login behoben

- Prepared Statements statt String-Konkatenation
- Betrifft Login und Passwort-Reset
- Fixes #247"
```

**In PhpStorm:**

1. Mache deine Änderungen im Code
2. Öffne das **Commit-Tool-Window** (`Ctrl+K` / `Cmd+K`)
3. Wähle nur die relevanten Dateien aus
4. Schreibe eine aussagekräftige Commit-Message
5. Nutze optional *Amend* wenn du nachbessern musst

---

### 3. Hotfix testen

Bevor du den Hotfix irgendwohin mergst, **teste ihn gründlich**:

- Führe deine automatisierten Tests aus
- Teste den spezifischen Fix manuell
- Wenn möglich, deploye auf eine Staging-Umgebung

**In PhpStorm kannst du Tests direkt ausführen:**
- Rechtsklick auf den Test-Ordner → *Run Tests*
- Oder nutze die Run-Konfiguration für PHPUnit

---

### 4. Hotfix in `main` mergen und taggen

Jetzt kommt der kritische Teil: Der Fix muss zurück nach `main`, damit er in die Produktion deployed werden kann.

**In PhpStorm:**

1. Wechsle zu `main` (Rechtsklick → *Checkout*)
2. Rechtsklick auf deinen Hotfix-Branch → *Merge into Current*
3. PhpStorm zeigt dir den Merge-Dialog – überprüfe die Änderungen
4. Bestätige den Merge

**Auf der Kommandozeile:**

```bash
# Zu main wechseln
git checkout main

# Hotfix mergen (mit --no-ff für einen expliziten Merge-Commit)
git merge --no-ff hotfix/kritischer-login-bug -m "Merge hotfix/kritischer-login-bug: SQL-Injection behoben"
```

**Warum `--no-ff`?** Die Option `--no-ff` (*no fast-forward*) erzwingt einen Merge-Commit, auch wenn ein Fast-Forward möglich wäre. Das hat zwei Vorteile:

1. Der Hotfix bleibt in der Historie als eigenständiger „Block" erkennbar
2. Du hast einen klaren Merge-Commit, der dokumentiert, wann der Hotfix eingespielt wurde

**Version-Tag erstellen:**

Nach einem Hotfix solltest du einen neuen Version-Tag erstellen:

```bash
# Tag erstellen
git tag -a v1.2.1 -m "Hotfix: SQL-Injection-Schwachstelle behoben"

# Tag auf GitHub pushen
git push origin v1.2.1
```

**In PhpStorm:**
- Menü: *Git* → *New Tag*
- Oder im Git-Log: Rechtsklick auf den Commit → *New Tag*

---

### 5. Hotfix in `develop` mergen ⚠️

**Das ist der Schritt, der am häufigsten vergessen wird!** Wenn du den Hotfix nicht auch in `develop` mergst, wird der Bug beim nächsten Release wieder auftauchen, weil `develop` dann ohne den Fix nach `main` gemergt wird.

**In PhpStorm:**

1. Wechsle zu `develop` (Rechtsklick → *Checkout*)
2. Rechtsklick auf `main` (oder den Hotfix-Branch) → *Merge into Current*
3. Löse eventuelle Konflikte im Merge-Tool

**Auf der Kommandozeile:**

```bash
git checkout develop
git pull origin develop  # Sicherstellen, dass develop aktuell ist
git merge --no-ff hotfix/kritischer-login-bug -m "Merge hotfix in develop: SQL-Injection behoben"
```

**Oder alternativ – den Tag mergen:**

```bash
git checkout develop
git merge --no-ff v1.2.1 -m "Merge v1.2.1 hotfix in develop"
```

---

### 6. Alles pushen

Jetzt müssen alle Änderungen auf GitHub landen:

```bash
# main mit dem Fix pushen
git push origin main

# develop mit dem Fix pushen
git push origin develop

# Tags pushen (falls noch nicht geschehen)
git push origin --tags
```

**In PhpStorm:**
- `Ctrl+Shift+K` / `Cmd+Shift+K` öffnet den Push-Dialog
- Stelle sicher, dass *Push Tags* aktiviert ist

---

### 7. Hotfix-Branch aufräumen

Nach erfolgreichem Merge in beide Branches kann der Hotfix-Branch gelöscht werden:

**Lokal löschen:**
```bash
git branch -d hotfix/kritischer-login-bug
```

**Auf GitHub löschen:**
```bash
git push origin --delete hotfix/kritischer-login-bug
```

**In PhpStorm:**
- Im Git-Tool-Window: Rechtsklick auf den Branch → *Delete*
- Häkchen setzen bei *Delete Tracking Branch* um ihn auch remote zu löschen

---

## Visualisierung des Workflows

```mermaid
flowchart TD
    subgraph Ausgangssituation
        A["main\n v1.2.0 - Bug vorhanden"]
        B["develop\n Aktive Entwicklung"]
        A -.->|"abgezweigt"| B
    end
    
    subgraph Hotfix-Prozess
        C["1. Hotfix-Branch erstellen\n von main"]
        D["2. Bug beheben\n und committen"]
        E["3. Testen"]
        F["4. Merge in main\n Tag: v1.2.1"]
        G["5. Merge in develop"]
        H["6. Push und Cleanup"]
    end
    
    A --> C
    C --> D
    D --> E
    E --> F
    F --> G
    G --> H
    
    style C fill:#ffcccc,color:#000000
    style F fill:#ccffcc,color:#000000
    style G fill:#ccffcc,color:#000000
```

**Die Commit-Historie nach dem Hotfix:**

```
main:    ──●──●──●─────────────●── (v1.2.1)
                 \            /
hotfix:           ●──●──●────┘
                        \
develop: ──●──●──●──●────●── (enthält den Fix)
```

---

## Umgang mit Konflikten beim Merge in `develop`

Es ist sehr wahrscheinlich, dass beim Merge des Hotfixes in `develop` Konflikte auftreten – schließlich wurde in `develop` weiterentwickelt, während der Hotfix auf dem älteren `main`-Stand basiert.

**Typisches Konflikt-Szenario:**

Der Hotfix hat eine Funktion in `LoginController.php` geändert, aber in `develop` wurde dieselbe Funktion für ein neues Feature erweitert.

**Konfliktlösung in PhpStorm:**

1. Nach dem Merge-Versuch zeigt PhpStorm die konfligierenden Dateien an
2. Doppelklick auf eine Datei öffnet den **3-Way-Merge-Editor**
3. Du siehst drei Spalten:
   - **Links (Yours):** Der aktuelle Stand von `develop`
   - **Rechts (Theirs):** Der Hotfix
   - **Mitte (Result):** Das gewünschte Ergebnis
4. Übernimm die Sicherheitsänderungen aus dem Hotfix
5. Behalte die neuen Features aus `develop`
6. Stelle sicher, dass beides zusammen funktioniert

**Wichtig:** Nach dem Lösen von Konflikten immer testen! Ein schlecht gelöster Konflikt kann den Fix unwirksam machen.

---

## Was ist mit Feature-Branches?

Wenn zum Zeitpunkt des Hotfixes Feature-Branches existieren, die von `develop` (oder `main`) abgezweigt wurden, enthalten auch diese den Bug. Hier gibt es mehrere Strategien:

### Option A: Feature-Branches auf `develop` rebasen

Nachdem der Hotfix in `develop` gemergt wurde, können Feature-Branch-Entwickler ihren Branch auf den neuen `develop`-Stand rebasen:

```bash
git checkout feature/neues-login
git fetch origin
git rebase origin/develop
```

**Vorteil:** Der Feature-Branch enthält automatisch den Fix.
**Nachteil:** Erfordert einen Force-Push, wenn der Branch bereits gepusht wurde.

### Option B: `develop` in Feature-Branches mergen

Alternativ können die Feature-Branch-Entwickler `develop` in ihren Branch mergen:

```bash
git checkout feature/neues-login
git fetch origin
git merge origin/develop
```

**Vorteil:** Kein Force-Push nötig.
**Nachteil:** Zusätzliche Merge-Commits.

### Option C: Nichts tun und beim PR lösen

Wenn der Feature-Branch ohnehin bald gemergt wird, kann der Konflikt auch beim finalen Merge/PR gelöst werden. Der Hotfix landet dann automatisch im Feature-Branch, sobald dieser nach `develop` gemergt wurde.

**Empfehlung:** Für sicherheitskritische Hotfixes ist Option A oder B besser, damit alle aktiv entwickelten Branches sofort geschützt sind.

---

## Der Hotfix-Workflow mit Pull Requests

In professionellen Teams wird der Hotfix-Workflow oft mit Pull Requests kombiniert, um Code Reviews auch für kritische Fixes sicherzustellen:

### Workflow mit PRs

1. **Hotfix-Branch erstellen und pushen:**
   ```bash
   git checkout -b hotfix/kritischer-bug main
   # Fix implementieren
   git push -u origin hotfix/kritischer-bug
   ```

2. **Ersten PR erstellen: Hotfix → main**
   - Auf GitHub: *New Pull Request*
   - Base: `main`, Compare: `hotfix/kritischer-bug`
   - Als *Critical* oder *Urgent* markieren
   - Reviewer zuweisen (idealerweise jemand, der sofort verfügbar ist)

3. **Review und Merge in main**
   - Schnelles, fokussiertes Review
   - Nach Approval: **Merge** (nicht Squash, damit der Commit-Hash erhalten bleibt)
   - Tag erstellen

4. **Zweiten PR erstellen: main → develop** (oder Hotfix → develop)
   - Base: `develop`, Compare: `main`
   - Dieser PR dokumentiert, dass der Hotfix auch in `develop` übernommen wurde

### Automatisierung mit GitHub Actions

Du kannst einen GitHub Actions Workflow erstellen, der automatisch einen PR von `main` nach `develop` erstellt, sobald ein Hotfix-Tag gepusht wird:

```yaml
name: Create Hotfix Backport PR

on:
  push:
    tags:
      - 'v*.*.*'  # Triggert bei Version-Tags

jobs:
  create-backport-pr:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          
      - name: Create Pull Request to develop
        uses: peter-evans/create-pull-request@v5
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          branch: backport/${{ github.ref_name }}-to-develop
          base: develop
          title: "Backport ${{ github.ref_name }} to develop"
          body: |
            Automatischer Backport-PR für Hotfix ${{ github.ref_name }}.
            
            **Bitte prüfen und mergen, damit der Fix in develop landet!**
          labels: hotfix, backport
```

---

## Checkliste für Hotfixes ✅

Hier ist eine praktische Checkliste, die du bei jedem Hotfix abarbeiten solltest:

### Vor dem Hotfix

- [ ] Bug ist verifiziert und reproduzierbar
- [ ] Priorität ist klar (ist es wirklich ein *kritischer* Hotfix?)
- [ ] Alle Stakeholder sind informiert

### Während des Hotfixes

- [ ] Hotfix-Branch von `main` erstellt (nicht von `develop`!)
- [ ] Nur den Bug behoben, keine anderen Änderungen
- [ ] Aussagekräftige Commit-Message mit Issue-Referenz
- [ ] Tests geschrieben oder angepasst
- [ ] Alle Tests laufen durch

### Nach dem Hotfix

- [ ] In `main` gemergt
- [ ] Version-Tag erstellt
- [ ] **In `develop` gemergt** ← *Wird am häufigsten vergessen!*
- [ ] Alle Branches gepusht
- [ ] Tag gepusht
- [ ] Hotfix-Branch gelöscht (lokal und remote)
- [ ] Deployment in Produktion durchgeführt
- [ ] Fix in Produktion verifiziert
- [ ] Team informiert (welche Feature-Branches sollten aktualisiert werden?)

---

## Häufige Fehler und wie du sie vermeidest

### Fehler 1: Hotfix von `develop` statt `main` erstellen

**Problem:** Du erstellst den Hotfix von `develop`, das bereits neue, ungetestete Features enthält. Wenn du jetzt nach `main` mergst, landen diese Features ungewollt in der Produktion.

**Lösung:** Immer zuerst `git checkout main` und dann den Hotfix-Branch erstellen.

### Fehler 2: Vergessen, den Hotfix in `develop` zu mergen

**Problem:** Der Fix ist in Produktion, aber bei der nächsten großen Release-Integration wird der Bug wieder eingeführt, weil `develop` den Fix nicht enthält.

**Lösung:** Nutze die Checkliste oder automatisiere den Backport mit GitHub Actions.

### Fehler 3: Zu viele Änderungen im Hotfix

**Problem:** Du nutzt den Hotfix als Gelegenheit, „schnell noch" andere Dinge zu fixen. Der Hotfix wird groß und schwer zu reviewen, das Risiko für neue Bugs steigt.

**Lösung:** Disziplin! Andere Fixes kommen in reguläre Feature-Branches. Ein Hotfix ist *nur* für den kritischen Bug.

### Fehler 4: Keinen Tag erstellen

**Problem:** Ohne Tag ist später schwer nachzuvollziehen, welcher exakte Code-Stand in Produktion deployed wurde.

**Lösung:** Immer einen semantischen Version-Tag erstellen (`v1.2.1` für einen Hotfix auf `v1.2.0`).

---

## Hotfixes in PhpStorm – Die wichtigsten Shortcuts

| Aktion | Windows/Linux | macOS |
|--------|---------------|-------|
| Git-Tool-Window öffnen | `Alt+9` | `Cmd+9` |
| Neuen Branch erstellen | `Ctrl+Shift+`` ` | `Cmd+Shift+`` ` |
| Commit-Dialog öffnen | `Ctrl+K` | `Cmd+K` |
| Push-Dialog öffnen | `Ctrl+Shift+K` | `Cmd+Shift+K` |
| Pull (Update Project) | `Ctrl+T` | `Cmd+T` |
| Branches anzeigen | `Ctrl+Shift+`` ` | `Cmd+Shift+`` ` |
| Git-Log anzeigen | `Alt+9`, dann Tab *Log* | `Cmd+9`, dann Tab *Log* |

---

## Zusammenfassung

Der Hotfix-Workflow folgt einem klaren Muster:

1. **Branch von `main`** – Nicht von `develop`!
2. **Minimal fixen** – Nur den Bug, nichts anderes
3. **Testen** – Automatisiert und manuell
4. **Merge in `main`** – Mit `--no-ff` und Version-Tag
5. **Merge in `develop`** – Der kritische Schritt, der oft vergessen wird
6. **Aufräumen** – Branch löschen, Team informieren

Mit diesem Workflow stellst du sicher, dass kritische Bugfixes schnell in die Produktion gelangen *und* in allen Entwicklungszweigen ankommen, sodass der Bug nicht bei der nächsten Release-Integration wieder auftaucht.

# Protected Branches auf GitHub – Dein Sicherheitsnetz für kritische Branches

Protected Branches sind eines der wichtigsten Features, um die Integrität deines Codes zu schützen. Sie verhindern, dass versehentlich (oder absichtlich) schädliche Änderungen direkt in wichtige Branches wie `main` oder `develop` gelangen. Stell dir Protected Branches als **Türsteher** vor, die genau prüfen, wer unter welchen Bedingungen Änderungen einbringen darf.

---

## Was sind Protected Branches? 🛡️

Ein Protected Branch ist ein Branch, für den du auf GitHub spezielle Regeln definierst, die eingehalten werden müssen, bevor Änderungen akzeptiert werden. Ohne Schutzregeln kann *jeder* mit Schreibrechten direkt auf `main` pushen – ein einziger falscher Befehl wie `git push --force origin main` könnte die gesamte Projekthistorie zerstören.

Mit Protected Branches kannst du unter anderem festlegen:

- **Wer** überhaupt auf den Branch pushen darf
- **Ob** direkte Pushes erlaubt sind oder nur über Pull Requests
- **Welche Prüfungen** (Tests, Reviews) bestanden sein müssen
- **Ob** die Branch-Historie überschrieben werden darf (Force Push)

### Warum sind sie so wichtig?

| Szenario ohne Schutz | Mögliche Konsequenz |
|---------------------|---------------------|
| Entwickler pusht ungetesteten Code direkt auf `main` | Produktionssystem fällt aus |
| Jemand führt versehentlich `git push --force` aus | Commit-Historie geht verloren |
| Merge ohne Code Review | Bugs und Sicherheitslücken gelangen unbemerkt in die Produktion |
| Commits von unverifizierten Accounts | Supply-Chain-Angriffe werden möglich |

---

## Die wichtigsten Schutzregeln im Detail

GitHub bietet eine Vielzahl von Schutzregeln, die du kombinieren kannst. Hier sind die wichtigsten mit Erklärungen und Empfehlungen:

### 1. „Require a pull request before merging"

Diese Regel ist das **Herzstück** der Branch Protection. Sie verhindert, dass irgendjemand direkt auf den geschützten Branch pushen kann. Alle Änderungen *müssen* über einen Pull Request laufen.

**Unteroptionen:**

- **„Require approvals"** – Legt fest, wie viele Personen den PR genehmigen müssen, bevor er gemerged werden kann. Du kannst 1 bis 6 erforderliche Approvals einstellen.
  
  *Empfehlung:* Für kleine Teams (1–3 Entwickler) reicht **1 Approval**. Für größere Teams oder kritische Projekte empfehle ich **2 Approvals**.

- **„Dismiss stale pull request approvals when new commits are pushed"** – Wenn diese Option aktiviert ist, werden bestehende Approvals *ungültig*, sobald neue Commits zum PR hinzugefügt werden. Das ist wichtig, weil ein Reviewer vielleicht Code genehmigt hat, der danach noch verändert wurde.
  
  *Empfehlung:* **Unbedingt aktivieren!** Sonst könnte jemand nach dem Approval noch problematischen Code hinzufügen.

- **„Require review from Code Owners"** – Wenn dein Repository eine `CODEOWNERS`-Datei hat, müssen die dort definierten Verantwortlichen den PR genehmigen. Mehr dazu später.

- **„Require approval of the most recent reviewable push"** – Verhindert, dass der Autor des letzten Commits seinen eigenen Code genehmigt (relevant, wenn Maintainer selbst zum PR beitragen).

### 2. „Require status checks to pass before merging"

Diese Regel stellt sicher, dass automatisierte Prüfungen (wie Tests oder Linting) erfolgreich durchlaufen müssen, bevor ein Merge möglich ist. Das ist dein **Qualitätstor**.

**Unteroptionen:**

- **„Require branches to be up to date before merging"** – Der Feature-Branch muss auf dem aktuellen Stand des Ziel-Branches sein. Das verhindert, dass Code gemerged wird, der zwar mit einer *alten* Version von `main` funktioniert, aber mit den neuesten Änderungen kollidieren könnte.
  
  *Empfehlung:* **Aktivieren**, auch wenn es manchmal nervig ist, den Branch aktualisieren zu müssen. Es verhindert das „aber auf meinem Branch hat es funktioniert"-Problem.

- **Status Checks auswählen** – Du wählst aus, welche Checks bestanden sein müssen. Diese erscheinen erst in der Liste, nachdem sie mindestens einmal gelaufen sind (z.B. durch einen GitHub Actions Workflow).

```yaml
# Beispiel: Workflow, der als Required Check verwendet werden kann
name: Tests
on: [push, pull_request]
jobs:
  phpunit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run PHPUnit
        run: vendor/bin/phpunit
```

### 3. „Require conversation resolution before merging"

Wenn Reviewer Kommentare oder Änderungswünsche hinterlassen, müssen diese als „resolved" markiert werden, bevor der PR gemerged werden kann. Das stellt sicher, dass **kein Feedback ignoriert** wird.

*Empfehlung:* **Aktivieren.** Es ist frustrierend als Reviewer, wenn deine Anmerkungen einfach übergangen werden.

### 4. „Require signed commits"

Mit dieser Regel müssen alle Commits kryptografisch signiert sein (GPG, SSH oder S/MIME). Signierte Commits zeigen auf GitHub ein grünes „Verified"-Badge und beweisen, dass der Commit wirklich von der angegebenen Person stammt.

*Empfehlung:* Für Open-Source-Projekte oder sicherheitskritische Anwendungen **empfohlen**. Für kleine, interne Projekte optional. Die Einrichtung erfordert etwas Aufwand (siehe Kapitel 10 des Kurses).

### 5. „Require linear history"

Diese Regel erzwingt eine lineare Commit-Historie, indem sie *nur* Squash-Merges oder Rebase-Merges erlaubt – keine regulären Merge-Commits mit zwei Parents.

*Empfehlung:* **Geschmackssache.** Eine lineare Historie ist übersichtlicher, aber manche Teams bevorzugen explizite Merge-Commits, weil sie zeigen, wann welcher Feature-Branch integriert wurde. Ich empfehle es für Projekte, die eine saubere, leicht lesbare Historie priorisieren.

### 6. „Do not allow bypassing the above settings"

Normalerweise können Repository-Administratoren alle Schutzregeln umgehen. Mit dieser Option wird **auch Admins** das Umgehen verboten.

*Empfehlung:* Für die meisten Projekte **nicht aktivieren**, da Admins manchmal legitime Gründe haben, Regeln zu umgehen (z.B. bei Notfall-Hotfixes). Bei sehr kritischen Projekten oder aus Compliance-Gründen kann es sinnvoll sein.

### 7. „Restrict who can push to matching branches"

Du kannst explizit definieren, welche Personen, Teams oder Apps überhaupt auf den Branch pushen dürfen (selbst über PRs). Das ist nützlich, wenn du z.B. nur bestimmten Maintainern erlauben möchtest, PRs zu mergen.

### 8. „Allow force pushes" und „Allow deletions"

Diese Optionen sind standardmäßig **deaktiviert** bei Protected Branches – und das sollte auch so bleiben!

- **Force Pushes** können die gesamte Commit-Historie überschreiben
- **Deletions** würden erlauben, den Branch komplett zu löschen

*Empfehlung:* **Niemals aktivieren** für `main` oder andere kritische Branches.

---

## Schritt-für-Schritt-Anleitung: Branch Protection einrichten

So richtest du die Schutzregeln für deinen `main`-Branch ein:

1. **Navigiere zu den Repository-Einstellungen**
   
   Öffne dein Repository auf GitHub und klicke auf **„Settings"** (Zahnrad-Symbol) in der oberen Navigationsleiste.

2. **Öffne die Branch-Einstellungen**
   
   In der linken Seitenleiste findest du unter „Code and automation" den Punkt **„Branches"**. Klicke darauf.

3. **Füge eine Branch Protection Rule hinzu**
   
   Unter „Branch protection rules" klickst du auf **„Add branch protection rule"** (oder „Add rule" bei neueren Rulesets).

4. **Definiere das Branch-Muster**
   
   Im Feld „Branch name pattern" gibst du den Namen des zu schützenden Branches ein. Du kannst:
   - Einen exakten Namen eingeben: `main`
   - Wildcards verwenden: `release/*` schützt alle Branches, die mit „release/" beginnen

5. **Wähle deine Schutzregeln**
   
   Aktiviere die gewünschten Optionen (siehe detaillierte Beschreibungen oben).

6. **Speichere die Regel**
   
   Scrolle nach unten und klicke auf **„Create"** oder **„Save changes"**.

---

## Empfohlene Konfiguration für verschiedene Szenarien

### 🧑‍💻 Solo-Entwickler (persönliches Projekt)

Auch als Solo-Entwickler können Protected Branches sinnvoll sein – sie schützen dich vor dir selbst!

| Regel | Empfehlung |
|-------|------------|
| Require pull request | ❌ Optional (kann Workflow verlangsamen) |
| Require status checks | ✅ Aktivieren, wenn du CI/CD hast |
| Require signed commits | ❌ Optional |
| Allow force pushes | ❌ Deaktiviert lassen |
| Allow deletions | ❌ Deaktiviert lassen |

Als Minimalkonfiguration empfehle ich: **Keine Force Pushes, keine Deletions, Status Checks falls vorhanden.**

### 👥 Kleines Team (2–5 Entwickler)

| Regel | Empfehlung |
|-------|------------|
| Require pull request | ✅ Aktivieren |
| Require approvals | ✅ 1 Approval |
| Dismiss stale approvals | ✅ Aktivieren |
| Require status checks | ✅ Aktivieren |
| Require branch up to date | ✅ Aktivieren |
| Require conversation resolution | ✅ Aktivieren |
| Allow force pushes | ❌ Deaktiviert |
| Allow deletions | ❌ Deaktiviert |

### 🏢 Größeres Team oder Open-Source-Projekt

| Regel | Empfehlung |
|-------|------------|
| Require pull request | ✅ Aktivieren |
| Require approvals | ✅ 2 Approvals |
| Dismiss stale approvals | ✅ Aktivieren |
| Require review from Code Owners | ✅ Aktivieren |
| Require status checks | ✅ Aktivieren (mehrere Checks) |
| Require branch up to date | ✅ Aktivieren |
| Require conversation resolution | ✅ Aktivieren |
| Require signed commits | ✅ Aktivieren |
| Require linear history | ⚠️ Team-Entscheidung |
| Do not allow bypassing | ⚠️ Bei hohen Compliance-Anforderungen |

---

## CODEOWNERS – Automatische Review-Zuweisung 📋

Die `CODEOWNERS`-Datei ist ein mächtiges Feature, das perfekt mit Protected Branches zusammenarbeitet. Du definierst darin, wer für welche Teile des Codes verantwortlich ist. Diese Personen werden automatisch als Reviewer zu PRs hinzugefügt, die „ihre" Dateien betreffen.

### So erstellst du eine CODEOWNERS-Datei

Die Datei muss an einem dieser Orte liegen:
- Repository-Root: `CODEOWNERS`
- `.github/CODEOWNERS` *(empfohlen)*
- `docs/CODEOWNERS`

### Syntax und Beispiele

```plaintext
# Dies ist ein Kommentar

# Standard-Owner für alles, was nicht anders definiert ist
* @standard-reviewer

# Bestimmte Dateien/Ordner einem Team zuweisen
/src/api/           @backend-team
/src/frontend/      @frontend-team

# Bestimmte Dateitypen
*.js                @javascript-expert
*.css               @design-team

# Kritische Konfigurationsdateien
/config/            @team-lead @senior-developer
.github/workflows/  @devops-team

# Bestimmte Dateien mehreren Reviewern zuweisen (alle müssen reviewen)
/security/          @security-team @team-lead
```

### CODEOWNERS in Kombination mit Branch Protection

Wenn du in den Branch Protection Rules **„Require review from Code Owners"** aktivierst, *müssen* die definierten Code Owners den PR genehmigen. Das stellt sicher, dass z.B.:

- Änderungen an der Datenbank-Schicht immer vom DBA geprüft werden
- Sicherheitskritischer Code vom Security-Team abgesegnet wird
- Frontend-Änderungen vom Frontend-Lead reviewed werden

---

## Rulesets – Die moderne Alternative 🆕

GitHub hat 2023 **Rulesets** eingeführt, eine modernere und flexiblere Alternative zu den klassischen Branch Protection Rules. Rulesets bieten einige Vorteile:

- **Auf Organisations-Ebene definierbar** – Eine Regel für alle Repositories
- **Bessere Wildcards** – Komplexere Muster möglich
- **Tag Protection** – Nicht nur Branches, auch Tags schützen
- **Bypass-Listen** – Feingranulare Kontrolle, wer Regeln umgehen darf

### Rulesets vs. klassische Branch Protection

| Feature | Branch Protection | Rulesets |
|---------|-------------------|----------|
| Repository-spezifisch | ✅ | ✅ |
| Organisations-weit | ❌ | ✅ |
| Tag Protection | ❌ | ✅ |
| Bypass-Ausnahmen | Eingeschränkt | Flexibel |
| API-Steuerung | ✅ | ✅ |

*Empfehlung:* Für einzelne Repositories sind die klassischen Branch Protection Rules einfacher. Für Organisationen mit vielen Repositories lohnt es sich, Rulesets zu evaluieren.

---

## Visualisierung: Typischer PR-Workflow mit Branch Protection

```mermaid
flowchart TD
    A["Entwickler erstellt\nFeature-Branch"] --> B["Entwickler pusht\nCommits"]
    B --> C["Pull Request\nwird erstellt"]
    C --> D{"Status Checks\nerfolgreich?"}
    D -->|Nein| E["Entwickler fixt\nProbleme"]
    E --> B
    D -->|Ja| F{"Code Review\nbestanden?"}
    F -->|Nein| G["Entwickler arbeitet\nFeedback ein"]
    G --> B
    F -->|Ja| H{"Branch\naktuell?"}
    H -->|Nein| I["Branch mit main\naktualisieren"]
    I --> D
    H -->|Ja| J{"Alle Conversations\nresolved?"}
    J -->|Nein| K["Offene Diskussionen\nklaeren"]
    K --> J
    J -->|Ja| L["Merge in main\nmoeglich"]
    L --> M["Feature-Branch\nloeschen"]

    style A fill:#e1f5fe,color:#000000
    style L fill:#c8e6c9,color:#000000
    style M fill:#c8e6c9,color:#000000
    style E fill:#ffcdd2,color:#000000
    style G fill:#ffcdd2,color:#000000
```

---

## Häufige Fragen und Probleme 🤔

### „Ich bin Admin, kann aber nicht pushen – warum?"

Wenn **„Do not allow bypassing the above settings"** aktiviert ist, gelten die Regeln auch für dich. Du musst dann ebenfalls einen PR erstellen.

### „Mein Status Check taucht nicht in der Auswahlliste auf"

Status Checks erscheinen erst, nachdem sie mindestens einmal gelaufen sind. Erstelle einen Test-PR oder pushe auf einen Testbranch, um den Workflow auszulösen.

### „Wie kann ich in einem Notfall trotzdem direkt pushen?"

Wenn du Admin bist und **„Do not allow bypassing"** *nicht* aktiviert ist, kannst du die Regeln umgehen. Alternativ:
1. Die Regel temporär deaktivieren
2. Den Notfall-Push machen
3. Die Regel sofort wieder aktivieren
4. **Dokumentieren**, warum das nötig war

*Besser:* Auch Notfall-Hotfixes über PRs, aber mit minimaler Review-Zeit und einem speziellen `hotfix`-Label.

### „Wie schütze ich mehrere Branches gleichzeitig?"

Nutze Wildcards im Branch-Pattern:
- `release/*` – Schützt alle Release-Branches
- `main` und `develop` – Erstelle zwei separate Regeln
- Mit Rulesets kannst du auch `main` und `develop` in einer Regel kombinieren

---

## Checkliste für deine Branch Protection 📝

Nutze diese Checkliste, um deine `main`-Branch-Protection einzurichten:

- [ ] Branch Protection Rule für `main` erstellt
- [ ] „Require pull request before merging" aktiviert
- [ ] Anzahl der erforderlichen Approvals festgelegt
- [ ] „Dismiss stale approvals" aktiviert
- [ ] Status Checks definiert (Tests, Linting)
- [ ] „Require branch to be up to date" aktiviert
- [ ] „Require conversation resolution" aktiviert
- [ ] Force Pushes deaktiviert (Standard)
- [ ] Branch-Deletion deaktiviert (Standard)
- [ ] CODEOWNERS-Datei erstellt (optional)
- [ ] Team über die neuen Regeln informiert

---

## Fazit

Protected Branches sind **kein bürokratisches Hindernis**, sondern ein essentielles Werkzeug für professionelle Softwareentwicklung. Sie schützen nicht nur vor böswilligen Änderungen, sondern vor allem vor Versehen und menschlichen Fehlern. Die initiale Einrichtung dauert nur wenige Minuten, kann aber stundenlange Debugging-Sessions oder sogar Datenverlust verhindern.

Selbst als Solo-Entwickler empfehle ich dir, zumindest Force Pushes und Deletions für `main` zu verbieten. Sobald du im Team arbeitest, sollten Pull Requests mit mindestens einem Review zur Pflicht werden. Die Zeit, die du in Reviews „verlierst", gewinnst du mehrfach zurück durch früh entdeckte Bugs und bessere Code-Qualität.

# Kapitel 3: Rebase meistern – Geschichte umschreiben wie ein Profi

**Einleitung:** `<span>git rebase</span>` ist einer der mächtigsten und gleichzeitig gefürchtetsten Befehle in Git. Richtig eingesetzt ermöglicht er dir eine saubere, lineare Commit-Historie ohne unnötige Merge-Commits. Mit Interactive Rebase kannst du sogar die Geschichte umschreiben: Commits zusammenfassen, aufteilen, umbenennen oder neu ordnen. Aber Vorsicht: Mit großer Macht kommt große Verantwortung! Das Umschreiben von bereits gepushter Historie kann zu ernsthaften Problemen führen. Dieses Kapitel lehrt dich, Rebase sicher und effektiv einzusetzen.

# Merge vs. Rebase – Der fundamentale Unterschied

Der Unterschied zwischen `git merge` und `git rebase` ist eines der wichtigsten Konzepte, das du als fortgeschrittener Git-Nutzer wirklich verstanden haben solltest. Beide Befehle dienen demselben Zweck – **Änderungen aus einem Branch in einen anderen zu integrieren** – aber sie tun dies auf völlig unterschiedliche Weise, mit unterschiedlichen Auswirkungen auf deine Commit-Historie.

---

## Die Ausgangssituation

Stellen wir uns folgendes Szenario vor: Du arbeitest an einem Feature-Branch, während dein Team weiterhin Commits auf `main` macht. Nach einiger Zeit sieht deine Repository-Struktur so aus:

```mermaid
gitGraph
    commit id: "A"
    commit id: "B"
    branch feature
    commit id: "X"
    commit id: "Y"
    checkout main
    commit id: "C"
    commit id: "D"
```

Du hast auf deinem `feature`-Branch die Commits **X** und **Y** erstellt, während auf `main` in der Zwischenzeit **C** und **D** hinzugekommen sind. Jetzt möchtest du die Änderungen von `main` in deinen Feature-Branch integrieren (oder umgekehrt deinen Feature-Branch in `main` einbringen). Hier kommen Merge und Rebase ins Spiel.

---

## Was passiert bei einem Merge?

Ein **Merge** erstellt einen neuen *Merge-Commit*, der die beiden Entwicklungslinien zusammenführt. Dieser Merge-Commit hat **zwei Eltern-Commits** – er verbindet buchstäblich die beiden Branches miteinander.

### Der Merge-Befehl

```bash
# Du bist auf feature und möchtest main integrieren
git checkout feature
git merge main
```

In PhpStorm: **Git → Merge... → main auswählen**

### Das Ergebnis nach dem Merge

```mermaid
gitGraph
    commit id: "A"
    commit id: "B"
    branch feature
    commit id: "X"
    commit id: "Y"
    checkout main
    commit id: "C"
    commit id: "D"
    checkout feature
    merge main id: "M" tag: "Merge-Commit"
```

Der neue Commit **M** ist der Merge-Commit. Er enthält keine eigenen Code-Änderungen (außer eventuell Konfliktlösungen), sondern dient als „Knotenpunkt", der die beiden Historien verbindet.

### Eigenschaften eines Merge

| Aspekt | Beschreibung |
|--------|--------------|
| **Commit-Historie** | Bleibt vollständig erhalten, zeigt aber eine verzweigte Struktur |
| **Originalcommits** | X und Y behalten ihre ursprünglichen SHA-Hashes |
| **Neuer Commit** | Ein Merge-Commit mit zwei Eltern wird erstellt |
| **Nicht-destruktiv** | Die bestehende Historie wird niemals verändert |

---

## Was passiert bei einem Rebase?

Ein **Rebase** nimmt deine Commits und „verpflanzt" sie auf eine neue Basis. Statt eines Merge-Commits werden deine Commits *neu erstellt* – sie werden auf den aktuellen Stand des Ziel-Branches aufgebaut, als hättest du erst jetzt mit der Arbeit begonnen.

### Der Rebase-Befehl

```bash
# Du bist auf feature und möchtest auf main "umbasieren"
git checkout feature
git rebase main
```

In PhpStorm: **Git → Rebase... → main auswählen**

### Was genau passiert beim Rebase?

Der Rebase-Prozess läuft in mehreren Schritten ab:

1. **Git identifiziert die Commits**, die nur auf `feature` existieren (X und Y)
2. **Git speichert diese Commits** temporär (als Patches)
3. **Git setzt den `feature`-Branch** auf die Spitze von `main` (Commit D)
4. **Git wendet die gespeicherten Commits** nacheinander wieder an

Das Ergebnis sind *neue Commits* **X'** und **Y'**, die denselben Inhalt haben wie die Originale, aber:
- einen **anderen Eltern-Commit** (sie bauen jetzt auf D auf, nicht auf B)
- einen **anderen SHA-Hash** (weil sich der Eltern-Commit geändert hat)
- einen **anderen Zeitstempel** (optional, je nach Konfiguration)

### Das Ergebnis nach dem Rebase

```mermaid
gitGraph
    commit id: "A"
    commit id: "B"
    commit id: "C"
    commit id: "D"
    commit id: "X'" tag: "neu erstellt"
    commit id: "Y'" tag: "neu erstellt"
```

Beachte: Die ursprünglichen Commits X und Y existieren technisch noch im Repository (erreichbar über `git reflog`), aber kein Branch zeigt mehr auf sie. Sie werden bei der nächsten Garbage Collection entfernt.

---

## Visueller Vergleich: Merge vs. Rebase

Um den Unterschied noch deutlicher zu machen, hier beide Ergebnisse im direkten Vergleich:

```mermaid
flowchart TB
    subgraph vorher["Ausgangssituation"]
        direction LR
        A1["A"] --> B1["B"]
        B1 --> C1["C"] --> D1["D"]
        B1 --> X1["X"] --> Y1["Y"]
    end
    
    subgraph merge["Nach git merge main"]
        direction LR
        A2["A"] --> B2["B"]
        B2 --> C2["C"] --> D2["D"]
        B2 --> X2["X"] --> Y2["Y"]
        D2 --> M["M"]
        Y2 --> M
    end
    
    subgraph rebase["Nach git rebase main"]
        direction LR
        A3["A"] --> B3["B"] --> C3["C"] --> D3["D"]
        D3 --> X3["X'"] --> Y3["Y'"]
    end
    
    vorher --> merge
    vorher --> rebase
    
    style M fill:#90EE90,color:#000000
    style X3 fill:#FFD700,color:#000000
    style Y3 fill:#FFD700,color:#000000
```

---

## Die tiefgreifenden Unterschiede im Detail

### 1. Integrität der Historie vs. Sauberkeit

**Merge** bewahrt die *exakte historische Wahrheit*: Es zeigt, dass parallel entwickelt wurde und wann die Zusammenführung stattfand. Das kann wertvoll sein, wenn du später nachvollziehen möchtest, wie die Entwicklung tatsächlich ablief.

**Rebase** erzeugt eine *saubere, lineare Historie*, als hätte es nie parallele Entwicklung gegeben. Das macht die Historie leichter lesbar, „lügt" aber technisch gesehen über den tatsächlichen Entwicklungsverlauf.

### 2. Commit-Identität

Dieser Punkt ist *entscheidend* für das Verständnis:

```bash
# Vor dem Rebase
git log --oneline feature
# abc1234 Y - Feature fertiggestellt
# def5678 X - Feature begonnen

# Nach dem Rebase
git log --oneline feature
# 111aaaa Y' - Feature fertiggestellt  ← NEUER HASH!
# 222bbbb X' - Feature begonnen        ← NEUER HASH!
```

Die Commits nach dem Rebase sind **komplett neue Git-Objekte**. Sie haben:
- denselben Autor
- dieselbe Commit-Message
- dieselben Code-Änderungen (Diffs)
- aber einen anderen SHA-Hash (weil der Parent-Commit anders ist)

### 3. Konfliktbehandlung

**Bei einem Merge** musst du Konflikte *einmal* lösen – im Merge-Commit. Die Konfliktlösung wird Teil dieses einen Commits.

**Bei einem Rebase** musst du Konflikte *für jeden Commit einzeln* lösen, während Git die Commits nacheinander auf die neue Basis anwendet. Das kann aufwändiger sein, führt aber dazu, dass jeder Commit für sich genommen „funktioniert".

```bash
# Rebase mit Konflikten
git rebase main
# CONFLICT in datei.php
# Konflikt lösen, dann:
git add datei.php
git rebase --continue
# Möglicherweise nächster Konflikt im nächsten Commit...
```

### 4. Auswirkungen auf andere Entwickler

Hier liegt der **kritischste Unterschied**:

| Szenario | Merge | Rebase |
|----------|-------|--------|
| Commits noch nicht gepusht | ✅ Sicher | ✅ Sicher |
| Commits bereits gepusht | ✅ Sicher | ⚠️ **Gefährlich!** |
| Andere arbeiten auf dem Branch | ✅ Sicher | ❌ **Sehr problematisch!** |

Wenn du Commits rebasst, die bereits gepusht wurden, änderst du deren SHA-Hashes. Wenn ein Kollege diese Commits bereits hat, entstehen *Duplikate* in der Historie, weil Git die alten und neuen Commits als unterschiedlich betrachtet.

---

## Wann sollte ich Merge verwenden?

**Merge ist die richtige Wahl, wenn:**

1. **Du öffentliche Historie integrierst** – Wenn du Änderungen aus `main` in deinen Feature-Branch holst und dieser Branch bereits gepusht wurde, ist Merge sicherer.

2. **Mehrere Entwickler am gleichen Branch arbeiten** – Ein Merge verändert keine bestehenden Commits, sodass niemand Probleme bekommt.

3. **Du die komplette Entwicklungshistorie bewahren möchtest** – Für Auditing oder Nachvollziehbarkeit kann die „echte" Historie wichtig sein.

4. **Du größere Feature-Branches in `main` integrierst** – Viele Teams bevorzugen hier einen Merge-Commit als „Markierung", dass ein Feature abgeschlossen wurde.

5. **Du unsicher bist** – Merge ist die sicherere Option. Im Zweifel: Merge verwenden.

### Beispiel: Feature in main mergen

```bash
git checkout main
git merge feature --no-ff  # --no-ff erzwingt einen Merge-Commit
```

Das `--no-ff` (no fast-forward) Flag ist wichtig, wenn du *explizit* einen Merge-Commit möchtest, auch wenn ein Fast-Forward möglich wäre.

---

## Wann sollte ich Rebase verwenden?

**Rebase ist die richtige Wahl, wenn:**

1. **Du deine lokale Arbeit aufräumen möchtest, bevor du sie teilst** – Vor dem ersten Push ist Rebase völlig sicher und macht deine Commits präsentabler.

2. **Du deinen Feature-Branch auf den neuesten Stand von `main` bringen möchtest** (und der Branch noch nicht gepusht wurde oder nur du daran arbeitest).

3. **Du eine lineare, saubere Historie bevorzugst** – Manche Teams haben die Konvention, dass Feature-Branches vor dem Merge rebased werden müssen.

4. **Du mit Interactive Rebase Commits zusammenfassen oder aufteilen möchtest** – Das ist nur mit Rebase möglich.

### Beispiel: Feature-Branch aktualisieren

```bash
git checkout feature
git fetch origin
git rebase origin/main
# Jetzt ist dein Feature-Branch auf dem neuesten Stand
```

In PhpStorm kannst du das über **Git → Rebase...** machen oder über das Git-Log-Fenster (Rechtsklick auf `origin/main` → „Rebase Current onto Selected").

---

## Der kombinierte Workflow: Das Beste aus beiden Welten

Viele professionelle Teams nutzen einen **kombinierten Workflow**, der die Vorteile beider Ansätze vereint:

```mermaid
flowchart TD
    A["Feature-Branch erstellen"] --> B["Lokal entwickeln\nmit kleinen Commits"]
    B --> C{"Bereit zum Teilen?"}
    C -->|Nein| B
    C -->|Ja| D["Interactive Rebase:\nCommits aufräumen"]
    D --> E["Rebase auf aktuellen main"]
    E --> F["Push Feature-Branch"]
    F --> G["Pull Request erstellen"]
    G --> H["Code Review"]
    H --> I{"Änderungen nötig?"}
    I -->|Ja| J["Änderungen committen"]
    J --> H
    I -->|Nein| K["Merge oder Squash-Merge\nin main"]
    
    style D fill:#FFD700,color:#000000
    style E fill:#FFD700,color:#000000
    style K fill:#90EE90,color:#000000
```

### Die Schritte im Detail

1. **Während der Entwicklung:** Mache so viele kleine Commits wie nötig, ohne dir über die „Schönheit" der Historie Gedanken zu machen.

2. **Vor dem Push:** Nutze Interactive Rebase (`git rebase -i`), um deine Commits aufzuräumen – Work-in-Progress-Commits zusammenfassen, Commit-Messages verbessern.

3. **Vor dem Pull Request:** Rebase auf den aktuellen `main`, um sicherzustellen, dass dein Branch sauber auf dem neuesten Stand aufbaut.

4. **Beim Mergen:** Der Pull Request wird entweder mit einem Merge-Commit oder einem Squash-Merge in `main` integriert.

---

## Die „goldene Regel" des Rebasings

> 🚨 **Rebase niemals Commits, die bereits gepusht wurden und an denen andere arbeiten könnten!**

Diese Regel ist so wichtig, dass sie einen eigenen Abschnitt verdient. Hier ist, was passiert, wenn du sie brichst:

### Das Katastrophen-Szenario

```mermaid
sequenceDiagram
    participant Du
    participant GitHub
    participant Kollege
    
    Du->>GitHub: Push commits X, Y
    GitHub->>Kollege: Pull commits X, Y
    Note over Kollege: Kollege arbeitet<br/>basierend auf Y
    Du->>Du: Rebase X, Y → X', Y'
    Du->>GitHub: Force Push X', Y'
    Note over GitHub: X, Y ersetzt durch X', Y'
    Kollege->>GitHub: Push neuer Commit Z
    Note over GitHub: KONFLIKT!<br/>Z basiert auf Y,<br/>aber Y existiert nicht mehr
    GitHub-->>Kollege: Rejected!
    Note over Kollege: Muss jetzt manuell<br/>reparieren 😱
```

Dein Kollege hat jetzt ein ernsthaftes Problem: Seine Arbeit basiert auf Commits, die in der offiziellen Historie nicht mehr existieren.

### Was tun, wenn es doch passiert ist?

Falls du versehentlich gepushte Commits rebased hast:

1. **Kommuniziere sofort mit deinem Team** – Informiere alle, die betroffen sein könnten.

2. **Koordiniere die Reparatur** – Alle müssen ihre lokalen Branches aktualisieren:
   ```bash
   git fetch origin
   git reset --hard origin/feature
   # ACHTUNG: Lokale Änderungen gehen verloren!
   ```

3. **Lerne daraus** – Richte Protected Branches ein, die Force-Push verbieten.

---

## Merge und Rebase in PhpStorm

### Merge durchführen

1. Stelle sicher, dass du auf dem Ziel-Branch bist (z.B. `feature`)
2. Gehe zu **Git → Merge...**
3. Wähle den Branch, den du einmergen möchtest (z.B. `main`)
4. Klicke auf **Merge**
5. Bei Konflikten öffnet sich der Merge-Dialog

### Rebase durchführen

1. Stelle sicher, dass du auf dem Branch bist, den du rebasen möchtest (z.B. `feature`)
2. Gehe zu **Git → Rebase...**
3. Wähle den Branch, auf den du rebasen möchtest (z.B. `main`)
4. Klicke auf **Rebase**
5. Bei Konflikten:
   - Löse jeden Konflikt im Editor
   - Klicke auf **Continue Rebase** in der Notification

### Tipp: Rebase über das Git-Log-Fenster

Eine besonders intuitive Methode in PhpStorm:

1. Öffne das **Git**-Tool-Window (Alt+9 / Cmd+9)
2. Sieh dir das Commit-Log an
3. Rechtsklicke auf den Commit, auf den du rebasen möchtest
4. Wähle **Rebase Current onto Selected**

---

## Praktisches Beispiel: Ein Tag im Leben eines Entwicklers

Lass mich einen typischen Workflow durchspielen:

### Morgens: Feature-Branch erstellen

```bash
git checkout main
git pull
git checkout -b feature/user-profile
```

### Während des Tages: Entwickeln mit vielen kleinen Commits

```bash
git commit -m "WIP: Grundstruktur angelegt"
git commit -m "WIP: Formular hinzugefügt"
git commit -m "Fix Typo"
git commit -m "WIP: Validierung"
git commit -m "Fertig mit Validierung"
```

### Vor dem Feierabend: Aufräumen mit Interactive Rebase

```bash
git rebase -i HEAD~5
# Im Editor: squash die WIP-Commits zusammen
# Ergebnis: 2 saubere Commits statt 5 chaotische
```

### Nächster Morgen: Auf aktuellen main rebasen

```bash
git fetch origin
git rebase origin/main
# Konflikte lösen, falls nötig
```

### Push und Pull Request

```bash
git push -u origin feature/user-profile
# Pull Request auf GitHub erstellen
```

### Nach dem Review: Merge in main

Auf GitHub: **Squash and Merge** oder **Merge Commit** – je nach Team-Konvention.

---

## Zusammenfassung: Merge vs. Rebase auf einen Blick

| Kriterium | Merge | Rebase |
|-----------|-------|--------|
| **Historie** | Verzweigt, zeigt parallele Entwicklung | Linear, als wäre sequentiell entwickelt worden |
| **Neue Commits** | Ein Merge-Commit | Keine neuen Commits (außer neu erstellten) |
| **Original-Commits** | Bleiben unverändert | Werden neu erstellt (neue SHA-Hashes) |
| **Sicherheit** | Immer sicher | Nur sicher für nicht-gepushte Commits |
| **Konfliktlösung** | Einmal im Merge-Commit | Für jeden Commit einzeln |
| **Lesbarkeit** | Kann unübersichtlich werden | Sehr übersichtlich |
| **Empfohlen für** | Öffentliche Branches, Team-Arbeit | Lokale Arbeit, Branch-Aktualisierung |

**Meine Empfehlung:** Starte mit Merge als Standard – es ist sicherer und verzeihender. Nutze Rebase gezielt für lokale Aufräumarbeiten und Branch-Aktualisierungen, *bevor* du pushst. Mit der Zeit wirst du ein Gefühl dafür entwickeln, wann welcher Ansatz am besten passt. 🎯

# Interactive Rebase meistern – Commits aufräumen wie ein Profi 🧹

Der **Interactive Rebase** ist eines der mächtigsten Werkzeuge in Git, um deine Commit-Historie nachträglich zu bearbeiten. Stell dir vor, du hättest eine Zeitmaschine für deine Commits: Du kannst sie umbenennen, zusammenfassen, aufteilen, neu ordnen oder komplett entfernen. Das Ergebnis ist eine saubere, verständliche Historie, die anderen (und deinem zukünftigen Ich) das Leben leichter macht.

---

## Was ist Interactive Rebase und wann nutze ich ihn?

Beim normalen `git rebase` wird dein Branch auf einen anderen Basis-Commit „umgepflanzt". Der **Interactive Rebase** (`git rebase -i`) geht einen Schritt weiter: Er öffnet einen Editor, in dem du für *jeden einzelnen Commit* entscheiden kannst, was damit passieren soll.

**Typische Anwendungsfälle:**

- Du hast mehrere kleine „WIP"-Commits gemacht und möchtest sie vor dem Push zu einem sauberen Commit zusammenfassen
- Eine Commit-Message enthält einen Tippfehler oder ist nicht aussagekräftig genug
- Du hast versehentlich etwas committed, das nicht in die Historie gehört
- Die Reihenfolge deiner Commits ergibt logisch keinen Sinn
- Du möchtest einen großen Commit nachträglich in kleinere, thematisch getrennte Commits aufteilen

> ⚠️ **Wichtig:** Interactive Rebase *schreibt die Historie um*. Das bedeutet, dass alle betroffenen Commits neue SHA-Hashes bekommen. **Nutze Interactive Rebase nur für Commits, die du noch nicht gepusht hast**, oder sei dir der Konsequenzen bewusst (Force-Push erforderlich, Probleme für andere Team-Mitglieder).

---

## Die Grundlagen: Interactive Rebase auf der Kommandozeile

Bevor wir zu PhpStorm kommen, ist es wichtig, die Grundlagen auf der Kommandozeile zu verstehen. Das hilft dir, zu verstehen, was PhpStorm im Hintergrund macht.

### Den Interactive Rebase starten

Um die letzten *n* Commits zu bearbeiten, verwendest du:

```bash
git rebase -i HEAD~n
```

Zum Beispiel, um die letzten 4 Commits zu bearbeiten:

```bash
git rebase -i HEAD~4
```

Alternativ kannst du auch einen spezifischen Commit-Hash angeben – dann werden alle Commits *nach* diesem Commit bearbeitet:

```bash
git rebase -i abc1234
```

### Was passiert nach dem Start?

Git öffnet deinen konfigurierten Editor (z.B. Vim, VS Code, oder den von PhpStorm) mit einer Liste aller betroffenen Commits. Diese Liste sieht ungefähr so aus:

```
pick a1b2c3d Feature: Login-Formular hinzugefügt
pick e4f5g6h WIP: Validierung angefangen
pick i7j8k9l Tippfehler korrigiert
pick m0n1o2p Validierung fertiggestellt

# Rebase abc1234..m0n1o2p onto abc1234 (4 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's message
# d, drop = remove commit
```

**Wichtig zu verstehen:** Die Commits werden in *chronologischer Reihenfolge* angezeigt (ältester oben, neuester unten) – das ist umgekehrt zur Anzeige in `git log`!

---

## Die sechs Rebase-Befehle im Detail

### 1. `pick` (p) – Commit unverändert übernehmen ✅

```
pick a1b2c3d Feature: Login-Formular hinzugefügt
```

**Was es macht:** Der Commit wird genau so übernommen, wie er ist – gleicher Inhalt, gleiche Message. Das ist die Standardoption.

**Wann verwenden:** Wenn ein Commit bereits perfekt ist und keine Änderung braucht.

### 2. `reword` (r) – Commit-Message ändern ✏️

```
reword a1b2c3d Feature: Login-Formular hinzugefügt
```

**Was es macht:** Der Commit-Inhalt bleibt unverändert, aber nach dem Speichern der Rebase-Liste öffnet Git einen Editor, in dem du die Commit-Message bearbeiten kannst.

**Wann verwenden:**
- Tippfehler in der Commit-Message korrigieren
- Eine aussagekräftigere Beschreibung nachträglich hinzufügen
- Ticket-Nummern oder Issue-Referenzen ergänzen

**Beispiel-Workflow:**
1. Du änderst `pick` zu `reword` in der Liste
2. Du speicherst und schließt die Liste
3. Git öffnet einen neuen Editor mit der alten Commit-Message
4. Du bearbeitest die Message und speicherst
5. Git setzt den Rebase fort

### 3. `edit` (e) – Commit anhalten und bearbeiten 🛠️

```
edit a1b2c3d Feature: Login-Formular hinzugefügt
```

**Was es macht:** Git wendet den Commit an und *pausiert dann*. Du befindest dich in einem Zustand, in dem du:
- Dateien ändern kannst
- Weitere Commits hinzufügen kannst
- Den bestehenden Commit mit `git commit --amend` modifizieren kannst
- Den Commit sogar in mehrere Commits aufteilen kannst

Nach deinen Änderungen setzt du den Rebase mit `git rebase --continue` fort.

**Wann verwenden:**
- Einen großen Commit in mehrere kleinere aufteilen
- Eine vergessene Datei nachträglich zu einem Commit hinzufügen
- Eine Datei aus einem Commit entfernen (aber behalten)

**Beispiel: Einen Commit in zwei aufteilen:**

```bash
# Nach dem Pausieren beim edit-Commit:

# Schritt 1: Den Commit "aufmachen", aber Änderungen behalten
git reset HEAD~1

# Schritt 2: Selektiv committen
git add src/Login/Form.php
git commit -m "Feature: Login-Formular HTML-Struktur"

git add src/Login/Validation.php
git commit -m "Feature: Login-Formular Validierung"

# Schritt 3: Rebase fortsetzen
git rebase --continue
```

### 4. `squash` (s) – Mit vorherigem Commit verschmelzen 🔗

```
pick a1b2c3d Feature: Login-Formular hinzugefügt
squash e4f5g6h WIP: Validierung angefangen
squash i7j8k9l Validierung fertiggestellt
```

**Was es macht:** Der Commit wird mit dem *vorherigen* Commit (dem darüber in der Liste) verschmolzen. Die Änderungen beider Commits werden kombiniert. Git öffnet dann einen Editor, in dem du eine neue, kombinierte Commit-Message schreiben kannst – dabei siehst du die Messages aller beteiligten Commits.

**Wann verwenden:**
- Mehrere „Work in Progress"-Commits zu einem sauberen Commit zusammenfassen
- Commits, die logisch zusammengehören, vereinen
- Die Historie vor dem Push aufräumen

**Der kombinierte Message-Editor sieht so aus:**

```
# This is a combination of 3 commits.
# This is the 1st commit message:

Feature: Login-Formular hinzugefügt

# This is the commit message #2:

WIP: Validierung angefangen

# This is the commit message #3:

Validierung fertiggestellt

# Please enter the commit message for your changes.
```

Du löschst oder kommentierst die nicht benötigten Zeilen aus und schreibst eine saubere, neue Message:

```
Feature: Login-Formular mit Validierung hinzugefügt

- Formular-HTML-Struktur erstellt
- Client-seitige Validierung implementiert
- Server-seitige Validierung hinzugefügt
```

### 5. `fixup` (f) – Verschmelzen ohne Message 🧽

```
pick a1b2c3d Feature: Login-Formular hinzugefügt
fixup e4f5g6h WIP
fixup i7j8k9l Tippfehler
```

**Was es macht:** Genau wie `squash`, aber die Commit-Message des fixup-Commits wird *automatisch verworfen*. Es wird nur die Message des Basis-Commits (des `pick`-Commits darüber) verwendet. Kein Editor öffnet sich für die Message-Bearbeitung.

**Wann verwenden:**
- Kleine Korrekturen, die zu einem vorherigen Commit gehören
- „Oops, das habe ich vergessen"-Commits
- Commits mit wenig aussagekräftigen Messages wie „WIP", „fix", „typo"

**Tipp:** Du kannst im Voraus Commits erstellen, die für Fixup gedacht sind, mit:

```bash
git commit --fixup=abc1234
```

Git erstellt dann automatisch einen Commit mit der Message `fixup! <Original-Message>`. Später kannst du mit `git rebase -i --autosquash` diese Fixup-Commits automatisch an die richtige Stelle sortieren lassen!

### 6. `drop` (d) – Commit komplett entfernen 🗑️

```
pick a1b2c3d Feature: Login-Formular hinzugefügt
drop e4f5g6h Debug-Code versehentlich committed
pick i7j8k9l Validierung fertiggestellt
```

**Was es macht:** Der Commit wird komplett aus der Historie entfernt. Die Änderungen dieses Commits sind danach *nicht mehr vorhanden* – weder die Dateien noch die Commit-Message.

**Wann verwenden:**
- Ein Commit enthält Testcode oder Debug-Ausgaben, die nicht in die Historie gehören
- Ein Commit war ein Fehler und sollte nie existiert haben
- Du möchtest Rauschen aus der Historie entfernen

**Alternativ:** Du kannst auch einfach die Zeile mit dem Commit aus der Liste löschen – das hat denselben Effekt wie `drop`.

> ⚠️ **Vorsicht:** Wenn spätere Commits von den Änderungen des gelöschten Commits abhängen, wirst du Konflikte bekommen!

---

## Commits umsortieren

Eine oft übersehene Funktion: Du kannst die Reihenfolge der Zeilen in der Rebase-Liste einfach ändern, um Commits umzusortieren!

**Vorher:**
```
pick a1b2c3d Feature A
pick e4f5g6h Feature C
pick i7j8k9l Feature B
```

**Nachher:**
```
pick a1b2c3d Feature A
pick i7j8k9l Feature B
pick e4f5g6h Feature C
```

**Wann verwenden:**
- Commits in eine logischere Reihenfolge bringen
- Verwandte Commits zusammenbringen, bevor du sie squashst
- Die Historie für Code-Reviews verständlicher machen

> ⚠️ **Vorsicht:** Wenn Commits voneinander abhängen (Commit B ändert Code, den Commit A eingeführt hat), kann das Umsortieren zu Konflikten führen.

---

## Übersicht: Alle Befehle auf einen Blick

| Befehl | Kurzform | Änderungen | Message | Wann verwenden |
|--------|----------|------------|---------|----------------|
| `pick` | `p` | behalten | behalten | Commit ist perfekt |
| `reword` | `r` | behalten | ändern | Message korrigieren |
| `edit` | `e` | pausieren & ändern | ändern möglich | Commit aufteilen/erweitern |
| `squash` | `s` | mit vorherigem vereinen | kombinieren | Commits zusammenfassen |
| `fixup` | `f` | mit vorherigem vereinen | verwerfen | Kleine Korrekturen |
| `drop` | `d` | entfernen | entfernen | Commit löschen |

---

## Interactive Rebase in PhpStorm 🛠️

PhpStorm bietet eine komfortable grafische Oberfläche für Interactive Rebase, die besonders für Einsteiger viel zugänglicher ist als die Kommandozeile.

### Methode 1: Über das Git-Log-Fenster

1. **Git-Log öffnen:** Gehe zu **View → Tool Windows → Git** oder klicke unten auf den Reiter „Git"

2. **Den Basis-Commit finden:** Scrolle im Log zu dem Commit, *ab dem* du die Historie bearbeiten möchtest (der Commit selbst wird nicht bearbeitet, nur alle danach)

3. **Kontextmenü öffnen:** Rechtsklick auf diesen Commit

4. **Rebase starten:** Wähle **„Interactively Rebase from Here..."**

   ```
   ┌─────────────────────────────────────────┐
   │  Commit abc1234                         │
   │  ─────────────────────────              │
   │  → Show Diff                            │
   │  → Copy Revision Number                 │
   │  → Create Branch...                     │
   │  → Checkout Revision                    │
   │  → Interactively Rebase from Here... ◄──│
   │  → Reset Current Branch to Here...      │
   └─────────────────────────────────────────┘
   ```

### Methode 2: Über das Hauptmenü

1. Gehe zu **Git → Rebase...**
2. Wähle im Dialog die Option **„Interactive"** 
3. Wähle den Basis-Commit oder Branch aus

### Methode 3: Über den aktuellen Branch

1. Klicke unten rechts auf den Branch-Namen in der Statusleiste
2. Rechtsklick auf deinen aktuellen Branch
3. Wähle **„Rebase Current onto Selected..."** und dann die Interactive-Option

### Der PhpStorm Interactive Rebase Dialog

Nach dem Starten öffnet sich ein übersichtlicher Dialog:

```
┌─────────────────────────────────────────────────────────────────────┐
│  Rebasing 4 commits                                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │ ▼ Action  │ Commit          │ Message                       │   │
│  ├───────────┼─────────────────┼───────────────────────────────┤   │
│  │ pick    ▼ │ a1b2c3d         │ Feature: Login-Formular       │   │
│  │ squash  ▼ │ e4f5g6h         │ WIP: Validierung              │   │
│  │ fixup   ▼ │ i7j8k9l         │ typo                          │   │
│  │ pick    ▼ │ m0n1o2p         │ Validierung fertig            │   │
│  └───────────┴─────────────────┴───────────────────────────────┘   │
│                                                                     │
│  [↑ Move Up]  [↓ Move Down]  [Show Diff]                           │
│                                                                     │
├─────────────────────────────────────────────────────────────────────┤
│                              [Cancel]  [Start Rebasing]             │
└─────────────────────────────────────────────────────────────────────┘
```

### Aktionen in PhpStorm ausführen

**Action ändern:** Klicke auf das Dropdown-Menü in der „Action"-Spalte neben jedem Commit. Du siehst alle verfügbaren Optionen:
- pick
- edit
- reword (wird in PhpStorm manchmal als „Edit Message" bezeichnet)
- squash
- fixup
- drop (oder „Skip")

**Commits umsortieren:** Wähle einen Commit aus und nutze die Buttons **„Move Up"** und **„Move Down"** oder ziehe die Commits per Drag & Drop an die gewünschte Position

**Commit-Diff ansehen:** Wähle einen Commit und klicke auf **„Show Diff"**, um zu sehen, welche Änderungen dieser Commit enthält – sehr hilfreich, um zu entscheiden, was damit passieren soll

**Commit-Message vorab bearbeiten:** Bei `reword` kannst du in manchen PhpStorm-Versionen die neue Message direkt im Dialog eingeben, ohne dass später ein separater Editor öffnet

### Schritt-für-Schritt: Commits in PhpStorm squashen

Hier ein konkretes Beispiel – du hast diese Commits und möchtest sie aufräumen:

```
pick a1b2c3d Feature: User-Profil-Seite erstellt
pick e4f5g6h WIP
pick i7j8k9l noch mehr WIP
pick m0n1o2p Profil-Seite fertig
```

**Ziel:** Alle vier Commits zu einem einzigen, sauberen Commit zusammenfassen.

1. **Rebase starten:** Rechtsklick auf den Commit *vor* `a1b2c3d` → „Interactively Rebase from Here..."

2. **Actions konfigurieren:**
   
   | Commit | Aktion | Warum |
   |--------|--------|-------|
   | a1b2c3d | `pick` | Basis-Commit, behält die Message |
   | e4f5g6h | `fixup` | Soll zu a1b2c3d hinzugefügt werden, Message verwerfen |
   | i7j8k9l | `fixup` | Soll auch hinzugefügt werden, Message verwerfen |
   | m0n1o2p | `fixup` | Letzter Teil, Message verwerfen |

3. **„Start Rebasing" klicken**

4. **Ergebnis:** Ein einziger Commit `a1b2c3d` mit der Message „Feature: User-Profil-Seite erstellt", der alle Änderungen enthält

**Wenn du `squash` statt `fixup` verwendest:** PhpStorm öffnet nach dem Rebase einen Editor, in dem du eine neue, kombinierte Message schreiben kannst.

### Schritt-für-Schritt: Commit-Message in PhpStorm ändern

1. **Rebase starten**

2. **Action auf `reword` setzen:** Ändere die Action für den gewünschten Commit zu „reword"

3. **„Start Rebasing" klicken**

4. **Message bearbeiten:** PhpStorm öffnet einen Commit-Message-Editor für jeden `reword`-Commit

   ```
   ┌─────────────────────────────────────────────────┐
   │  Edit Commit Message                            │
   ├─────────────────────────────────────────────────┤
   │  ┌─────────────────────────────────────────┐    │
   │  │ Feature: Login-Formular mit Validierung │    │
   │  │                                         │    │
   │  │ - Email-Validierung hinzugefügt         │    │
   │  │ - Passwort-Stärke-Check implementiert   │    │
   │  │ - Fehlerbehandlung verbessert           │    │
   │  └─────────────────────────────────────────┘    │
   │                                                 │
   │                    [Cancel]  [Save Message]     │
   └─────────────────────────────────────────────────┘
   ```

5. **Speichern:** Klicke auf „Save Message" oder drücke Ctrl+Enter

### Den `edit`-Befehl in PhpStorm nutzen

Der `edit`-Befehl ist etwas spezieller, weil er den Rebase *pausiert* und dir Kontrolle gibt:

1. **Action auf `edit` setzen und Rebase starten**

2. **PhpStorm-Benachrichtigung:** Du siehst eine Notification:
   
   > „Interactive rebase stopped. You can amend the commit or continue rebasing."

3. **Änderungen vornehmen:** 
   - Dateien bearbeiten
   - Neue Dateien zur Staging Area hinzufügen
   - `Commit` (ohne Push!) um den aktuellen Commit zu erweitern
   - Oder im Terminal: `git commit --amend`

4. **Rebase fortsetzen:** 
   - Über die Notification: Klicke auf **„Continue Rebasing"**
   - Über das Menü: **Git → Continue Rebasing**
   - Im Terminal: `git rebase --continue`

---

## Konflikte während des Interactive Rebase

Beim Umsortieren oder Squashen kann es zu Konflikten kommen, besonders wenn Commits voneinander abhängen.

### Konflikte erkennen

PhpStorm zeigt dir eine klare Meldung:

```
┌─────────────────────────────────────────────────────────────┐
│  ⚠ Rebasing conflict                                        │
│                                                             │
│  Conflicts in the following files:                          │
│  • src/Login/Form.php                                       │
│  • src/Login/Validation.php                                 │
│                                                             │
│  [Resolve Conflicts]  [Abort Rebase]                        │
└─────────────────────────────────────────────────────────────┘
```

### Konflikte lösen

1. **„Resolve Conflicts" klicken:** Der Merge-Dialog öffnet sich

2. **3-Way-Merge nutzen:** PhpStorm zeigt dir:
   - **Left (Yours/HEAD):** Der aktuelle Stand nach den bisherigen Rebase-Schritten
   - **Right (Theirs):** Die Änderungen aus dem Commit, der gerade angewendet wird
   - **Center (Result):** Das Ergebnis, das du zusammenbaust

3. **Konflikte einzeln lösen:** Nutze die Buttons „Accept Left", „Accept Right" oder bearbeite manuell

4. **Alle Konflikte gelöst:** Klicke auf **„Apply"**

5. **Rebase fortsetzen:** Klicke auf **„Continue Rebasing"** in der Notification

### Rebase abbrechen

Wenn du merkst, dass der Rebase in eine Sackgasse führt:

- **In PhpStorm:** Klicke auf „Abort Rebase" oder gehe zu **Git → Abort Rebasing**
- **Im Terminal:** `git rebase --abort`

Das stellt den exakten Zustand vor dem Rebase wieder her – als wäre nichts passiert.

---

## Praktisches Beispiel: Kompletter Workflow

Hier ein realistisches Szenario, das alles zusammenbringt:

### Ausgangssituation

Du hast an einem Feature gearbeitet und folgende Commits erstellt:

```
1. abc1234 - WIP: Angefangen mit der API
2. def5678 - API Endpunkt erstellt  
3. ghi9012 - FIXME: Debug-Ausgaben drin!!!
4. jkl3456 - API fertig, aber Typo in Message
5. mno7890 - Dokumentation hinzugefügt
```

### Ziel

Bevor du pushst, möchtest du:
- Die ersten zwei Commits zusammenfassen
- Den Debug-Commit entfernen
- Den Typo in Commit 4 korrigieren
- Die Dokumentation nach oben verschieben (vor die API-Commits)

### Schritt für Schritt in PhpStorm

1. **Rebase starten:** Rechtsklick auf den Commit *vor* abc1234 → „Interactively Rebase from Here..."

2. **Dialog konfigurieren:**

   | Original | Aktion | Neue Position | Ergebnis |
   |----------|--------|---------------|----------|
   | mno7890 | pick | ↑ nach oben verschoben | bleibt |
   | abc1234 | pick | — | wird Basis für squash |
   | def5678 | squash | — | verschmilzt mit abc1234 |
   | ghi9012 | drop | — | wird entfernt |
   | jkl3456 | reword | — | Message wird korrigiert |

   Die neu sortierte Liste sieht so aus:
   ```
   pick    mno7890  Dokumentation hinzugefügt
   pick    abc1234  WIP: Angefangen mit der API
   squash  def5678  API Endpunkt erstellt
   reword  jkl3456  API fertig, aber Typo in Message
   ```

3. **„Start Rebasing" klicken**

4. **Squash-Message bearbeiten:** PhpStorm öffnet einen Editor für die kombinierte Message:
   
   ```
   Feature: REST-API implementiert
   
   - Endpunkt /api/users erstellt
   - Authentifizierung hinzugefügt
   - Response-Format definiert
   ```

5. **Reword-Message bearbeiten:** Danach öffnet sich der Editor für den Typo-Fix

6. **Ergebnis:** Saubere Historie mit drei aussagekräftigen Commits:
   ```
   1. Dokumentation hinzugefügt
   2. Feature: REST-API implementiert
   3. API-Tests und finale Korrekturen
   ```

---

## Best Practices für Interactive Rebase 📋

**Vor dem Rebase:**
- Stelle sicher, dass dein Working Directory „clean" ist (keine uncommitteten Änderungen)
- Erstelle ggf. einen Backup-Branch: `git branch backup-vor-rebase`
- Nutze Interactive Rebase *vor* dem Push, nicht danach

**Während des Rebase:**
- Arbeite von oben nach unten durch die Liste
- Squashe immer in den älteren Commit (der oben steht)
- Bei Unsicherheit: lieber abbrechen und neu starten
- Nutze `edit` sparsam – er ist mächtig, aber auch komplex

**Commit-Messages nach dem Squash:**
- Schreibe eine neue, zusammenfassende Message
- Lösche die alten WIP-Messages komplett
- Folge dem Conventional Commits Format, wenn dein Team es nutzt

**Wenn etwas schiefgeht:**
- `git rebase --abort` ist dein Freund
- Das Reflog hat alles gespeichert: `git reflog` zeigt dir alle Zustände
- Mit `git reset --hard HEAD@{n}` kannst du zu jedem vorherigen Zustand zurück

---

## Zusammenfassung

Interactive Rebase ist ein unverzichtbares Werkzeug für professionelle Git-Nutzung. Mit den sechs Befehlen `pick`, `reword`, `edit`, `squash`, `fixup` und `drop` kannst du deine Commit-Historie perfekt aufräumen, bevor du sie mit der Welt teilst. PhpStorm macht diesen Prozess durch seine grafische Oberfläche besonders zugänglich – du siehst auf einen Blick alle Commits, kannst Actions per Dropdown wählen und Commits per Drag & Drop umsortieren.

**Die wichtigsten Takeaways:**

- 🎯 **Nutze Interactive Rebase nur für lokale, noch nicht gepushte Commits**
- 🔗 **Squash/Fixup** sind deine Werkzeuge für saubere, atomare Commits
- ✏️ **Reword** ermöglicht nachträgliche Message-Korrekturen ohne Aufwand
- 🛑 **Drop** entfernt ungewollte Commits komplett aus der Historie
- 🔧 **Edit** gibt dir volle Kontrolle, ist aber auch am komplexesten
- ↩️ **Im Zweifel:** `git rebase --abort` und von vorne beginnen

# Commits zusammenfassen (Squash) in PhpStorm 🔧

Du hast also fleißig gearbeitet und dabei mehrere kleine Commits erstellt – vielleicht mit Nachrichten wie *„WIP"*, *„Fix typo"*, *„Noch ein Fix"* oder *„Jetzt aber wirklich fertig"*. Bevor du diese „Commits des Schreckens" auf GitHub pushst, möchtest du sie zu einem einzigen, sauberen Commit zusammenfassen. Das ist nicht nur guter Stil, sondern macht die Historie für dich und dein Team deutlich lesbarer. Lass mich dich Schritt für Schritt durch den gesamten Prozess führen.

---

## Warum überhaupt Commits squashen?

Bevor wir in die Praxis einsteigen, kurz zum **Warum**: Eine saubere Commit-Historie ist wie ein gut geschriebenes Tagebuch deines Projekts. Wenn du später (oder ein Kollege) nachvollziehen möchtest, welche Änderung wann und warum gemacht wurde, helfen aussagekräftige Commits enorm. Niemand möchte sich durch zehn *„WIP"*-Commits wühlen, um zu verstehen, was eigentlich passiert ist.

**Vorteile des Squashens:**
- **Lesbarkeit:** Ein Commit pro logischer Änderung statt vieler Mikro-Commits
- **Einfacheres Debugging:** Mit `git bisect` findest du Bugs schneller, wenn jeder Commit einen sinnvollen Zustand repräsentiert
- **Saubere Pull Requests:** Reviewer können die Änderungen besser nachvollziehen
- **Professioneller Eindruck:** Zeigt, dass du dir Gedanken über deine Arbeit machst

---

## Die Ausgangssituation

Nehmen wir an, du hast in den letzten Stunden an einem Login-Formular gearbeitet und dabei folgende Commits erstellt (vom ältesten zum neuesten):

```
a1b2c3d  WIP: Login-Formular angefangen
d4e5f6g  Passwort-Feld hinzugefügt
h7i8j9k  Typo im Label gefixt
l0m1n2o  CSS angepasst
p3q4r5s  Validation hinzugefügt
```

Diese fünf Commits sollen zu einem einzigen werden:

```
x9y8z7w  feat: Login-Formular mit Validierung implementiert
```

---

## Methode 1: Der komfortable Weg über das Git-Log in PhpStorm

Dies ist die **empfohlene Methode** für die meisten Situationen, da sie visuell und intuitiv ist.

1. **Öffne das Git-Tool-Window**
   
   Navigiere zu **View → Tool Windows → Git** oder nutze die Tastenkombination `Alt + 9` (Windows/Linux) bzw. `⌘ + 9` (macOS). Du siehst jetzt das Git-Log mit allen Commits deines Repositories.

2. **Filtere auf deinen aktuellen Branch**
   
   In der Toolbar des Git-Fensters siehst du ein Dropdown-Menü für Branches. Stelle sicher, dass du nur die Commits deines Feature-Branches siehst. Du kannst auch im Suchfeld nach deinem Branch-Namen filtern.

3. **Identifiziere den Commit-Bereich**
   
   Scrolle durch die Liste und identifiziere die Commits, die du zusammenfassen möchtest. In unserem Beispiel sind das die fünf Commits von *„WIP: Login-Formular angefangen"* bis *„Validation hinzugefügt"*.

4. **Wähle den Basis-Commit aus**
   
   Hier wird es wichtig: Du musst den Commit **vor** deinem ersten zu squashenden Commit finden. Das ist der Commit, auf dem deine Arbeit aufbaut – typischerweise der letzte Commit, bevor du mit dem Feature angefangen hast. Dieser Commit selbst wird **nicht** verändert, er dient nur als Referenzpunkt.

5. **Starte den Interactive Rebase**
   
   Klicke mit der **rechten Maustaste** auf diesen Basis-Commit und wähle im Kontextmenü: **Interactively Rebase from Here...** (auf Deutsch: *„Interaktiv Rebase von hier..."*).

6. **Das Interactive Rebase-Fenster**
   
   PhpStorm öffnet jetzt ein Fenster mit dem Titel *„Rebasing Commits"* oder *„Git Interactive Rebase"*. Du siehst eine Liste aller Commits, die nach deinem gewählten Basis-Commit kommen – also genau die Commits, die du bearbeiten möchtest. Die Liste zeigt:
   
   - Die **Aktion** (standardmäßig „pick" für jeden Commit)
   - Den **Commit-Hash** (verkürzt)
   - Die **Commit-Nachricht**

7. **Commits zum Squashen markieren**
   
   Jetzt kommt der entscheidende Schritt. Du hast mehrere Möglichkeiten, die Aktion für jeden Commit zu ändern:
   
   - **Doppelklick** auf die Aktion öffnet ein Dropdown-Menü
   - **Rechtsklick** auf einen Commit zeigt alle verfügbaren Aktionen
   - Wähle mehrere Commits mit `Strg + Klick` (Windows/Linux) oder `⌘ + Klick` (macOS) und ändere die Aktion für alle gleichzeitig
   
   Für das Squashen gehst du so vor:
   
   - Der **erste Commit** (der älteste, auf den die anderen aufbauen) behält die Aktion **„pick"**
   - Alle **folgenden Commits**, die du zusammenfassen möchtest, änderst du auf **„squash"** oder **„fixup"**

8. **Der Unterschied zwischen „squash" und „fixup"**
   
   | Aktion | Beschreibung |
   |--------|--------------|
   | **squash** | Kombiniert den Commit mit dem vorherigen. Die Commit-Nachricht wird beibehalten, und du kannst sie im nächsten Schritt bearbeiten. |
   | **fixup** | Kombiniert den Commit mit dem vorherigen, aber **verwirft** die Commit-Nachricht. Nutze das für Commits wie *„Typo gefixt"*, deren Nachricht nicht erhalten bleiben muss. |
   
   Für unser Beispiel könntest du es so konfigurieren:
   
   ```
   pick    a1b2c3d  WIP: Login-Formular angefangen
   squash  d4e5f6g  Passwort-Feld hinzugefügt
   fixup   h7i8j9k  Typo im Label gefixt
   squash  l0m1n2o  CSS angepasst
   squash  p3q4r5s  Validation hinzugefügt
   ```

9. **Starte den Rebase**
   
   Klicke auf **„Start Rebasing"** (oder den entsprechenden Button in deiner PhpStorm-Version). PhpStorm beginnt jetzt, die Commits neu zu schreiben.

10. **Bearbeite die kombinierte Commit-Nachricht**
    
    Nach dem Rebase öffnet PhpStorm ein Fenster, in dem du die **neue, kombinierte Commit-Nachricht** eingeben kannst. Du siehst dort alle Nachrichten der Commits, die mit „squash" markiert waren (nicht die mit „fixup" – deren Nachrichten wurden verworfen). Das sieht ungefähr so aus:
    
    ```
    # This is a combination of 5 commits.
    # The first commit's message is:
    WIP: Login-Formular angefangen
    
    # This is the 2nd commit message:
    Passwort-Feld hinzugefügt
    
    # This is the 4th commit message:
    CSS angepasst
    
    # This is the 5th commit message:
    Validation hinzugefügt
    ```
    
    **Lösche diesen ganzen Text** und schreibe stattdessen eine saubere, aussagekräftige Commit-Nachricht:
    
    ```
    feat: Login-Formular mit Validierung implementiert
    
    - Login-Formular mit Benutzername- und Passwort-Feld erstellt
    - Client-seitige Validierung für beide Felder hinzugefügt
    - CSS-Styling für konsistentes Design angepasst
    ```

11. **Bestätige die Nachricht**
    
    Klicke auf **„OK"** oder **„Commit"**, um den Prozess abzuschließen.

---

## Methode 2: Der schnelle Weg mit „Squash Commits" direkt in PhpStorm

PhpStorm bietet auch eine **direkte Squash-Funktion**, die noch schneller ist, wenn du weißt, welche Commits du zusammenfassen möchtest.

1. **Öffne das Git-Log** wie in Methode 1 beschrieben (`Alt + 9` oder `⌘ + 9`).

2. **Wähle die Commits aus**, die du zusammenfassen möchtest. Halte `Strg` (Windows/Linux) oder `⌘` (macOS) gedrückt und klicke auf jeden Commit, den du squashen möchtest. Die Commits müssen **aufeinanderfolgend** sein.

3. **Rechtsklick auf die Auswahl** und wähle im Kontextmenü: **Squash Commits...** (auf Deutsch: *„Commits zusammenfassen..."*).

4. **Bearbeite die Commit-Nachricht** im erscheinenden Dialog und bestätige mit **„OK"**.

Diese Methode ist schneller, bietet aber weniger Kontrolle als der Interactive Rebase – du kannst zum Beispiel nicht einzelne Commits mit „fixup" statt „squash" markieren.

---

## Methode 3: Über das Terminal in PhpStorm

Falls du lieber mit dem Terminal arbeitest oder die grafischen Methoden aus irgendeinem Grund nicht funktionieren, kannst du den Interactive Rebase auch direkt über die Kommandozeile durchführen.

1. **Öffne das Terminal in PhpStorm**
   
   Navigiere zu **View → Tool Windows → Terminal** oder nutze `Alt + F12` (Windows/Linux) bzw. `⌥ + F12` (macOS).

2. **Finde heraus, wie viele Commits du zusammenfassen möchtest**
   
   Mit `git log --oneline` siehst du eine kompakte Liste deiner letzten Commits:
   
   ```bash
   git log --oneline -10
   ```
   
   Das zeigt dir die letzten 10 Commits. Zähle, wie viele du squashen möchtest – in unserem Beispiel sind es 5.

3. **Starte den Interactive Rebase**
   
   ```bash
   git rebase -i HEAD~5
   ```
   
   Damit sagst du Git: „Ich möchte die letzten 5 Commits interaktiv bearbeiten."

4. **Bearbeite die Commit-Liste im Editor**
   
   Git öffnet einen Text-Editor (in PhpStorm normalerweise der integrierte Editor oder der von dir konfigurierte). Du siehst:
   
   ```
   pick a1b2c3d WIP: Login-Formular angefangen
   pick d4e5f6g Passwort-Feld hinzugefügt
   pick h7i8j9k Typo im Label gefixt
   pick l0m1n2o CSS angepasst
   pick p3q4r5s Validation hinzugefügt
   ```
   
   Ändere es zu:
   
   ```
   pick a1b2c3d WIP: Login-Formular angefangen
   squash d4e5f6g Passwort-Feld hinzugefügt
   fixup h7i8j9k Typo im Label gefixt
   squash l0m1n2o CSS angepasst
   squash p3q4r5s Validation hinzugefügt
   ```
   
   Du kannst auch die Kurzformen verwenden: `p` für pick, `s` für squash, `f` für fixup.

5. **Speichere und schließe den Editor**
   
   In PhpStorms integriertem Editor klickst du einfach auf den Bestätigungs-Button. Bei Vim-Editoren: `:wq` und Enter.

6. **Bearbeite die kombinierte Commit-Nachricht** wie bei Methode 1 beschrieben.

---

## Was passiert, wenn etwas schief geht? 🆘

Keine Panik! Ein Rebase kann abgebrochen werden, solange er noch läuft:

**Im Terminal:**
```bash
git rebase --abort
```

**In PhpStorm:** Wenn der Rebase fehlschlägt (z.B. wegen Konflikten), zeigt PhpStorm eine Benachrichtigung an. Du kannst dort **„Abort Rebase"** wählen, um zum Zustand vor dem Rebase zurückzukehren.

**Wenn du den Rebase schon abgeschlossen hast** und merkst, dass etwas nicht stimmt, hilft dir das **Reflog**:

```bash
git reflog
```

Dort siehst du alle Zustände deines Repositories, auch die vor dem Rebase. Mit `git reset --hard HEAD@{n}` (wobei `n` die Nummer aus dem Reflog ist) kannst du zu einem früheren Zustand zurückkehren.

---

## Wichtige Hinweise und Warnungen ⚠️

1. **Nur unpushte Commits squashen!**
   
   Die goldene Regel des Rebasings: **Schreibe niemals die Historie um, die bereits gepusht wurde** (es sei denn, du bist dir absolut sicher, was du tust, und hast das mit deinem Team abgesprochen). Wenn du bereits gepushte Commits squashst, müsstest du mit `git push --force` pushen, was die Historie für alle anderen Entwickler kaputt macht.

2. **Force-Push nur bei Feature-Branches**
   
   Wenn du an einem Feature-Branch arbeitest, den nur du nutzt, und du bereits gepusht hast, kannst du nach dem Squash einen Force-Push machen:
   
   ```bash
   git push --force-with-lease origin feature/login-formular
   ```
   
   `--force-with-lease` ist sicherer als `--force`, weil es prüft, ob jemand anders in der Zwischenzeit gepusht hat.

3. **Mache vorher ein Backup (optional)**
   
   Wenn du unsicher bist, erstelle vorher einen temporären Branch als Backup:
   
   ```bash
   git branch backup-vor-squash
   ```
   
   Falls etwas schief geht, kannst du immer zu diesem Branch zurückkehren.

---

## Ein Beispiel-Workflow zusammengefasst

```mermaid
flowchart TD
    A["Du hast 5 WIP-Commits\nim Feature-Branch"] --> B["Oeffne Git-Log\nAlt+9 oder Cmd+9"]
    B --> C["Finde den Commit VOR\ndeiner Arbeit"]
    C --> D["Rechtsklick:\nInteractively Rebase from Here"]
    D --> E["Markiere Commits\nmit squash oder fixup"]
    E --> F["Klicke: Start Rebasing"]
    F --> G["Schreibe eine saubere\nkombinierte Commit-Nachricht"]
    G --> H["Bestaetigen"]
    H --> I["Fertig: 1 sauberer Commit\nstatt 5 WIP-Commits"]
    
    style A fill:#ffcccc,color:#000000
    style I fill:#ccffcc,color:#000000
```

---

## Zusammenfassung

| Schritt | Aktion |
|---------|--------|
| 1 | Git-Log öffnen (`Alt + 9` / `⌘ + 9`) |
| 2 | Basis-Commit identifizieren (der Commit **vor** deiner Arbeit) |
| 3 | Rechtsklick → *„Interactively Rebase from Here..."* |
| 4 | Commits mit „squash" oder „fixup" markieren |
| 5 | *„Start Rebasing"* klicken |
| 6 | Neue, saubere Commit-Nachricht schreiben |
| 7 | Bestätigen – fertig! ✅ |

Mit etwas Übung wird das Squashen zur Routine und deine Commit-Historie wird es dir danken. Dein zukünftiges Ich (und deine Kollegen) werden dich dafür lieben, wenn sie durch eine saubere, nachvollziehbare Historie scrollen können, statt sich durch zwanzig *„WIP"*- und *„Fix"*-Commits zu kämpfen! 🎉

# Die „goldene Regel" des Rebasings 🔒

Die **goldene Regel des Rebasings** lässt sich in einem Satz zusammenfassen:

> *„Schreibe niemals die Historie von Commits um, die bereits gepusht und mit anderen geteilt wurden."*

Diese Regel ist so fundamental, dass sie in der Git-Community fast schon als Gesetz gilt. Um zu verstehen, *warum* diese Regel so wichtig ist und was passiert, wenn man sie bricht, müssen wir zunächst verstehen, was beim Rebase technisch passiert.

---

## Was passiert beim Rebase technisch?

Wenn du `git rebase` ausführst, **erstellt Git neue Commits** – auch wenn der Inhalt identisch bleibt. Das liegt daran, wie Git-Commits aufgebaut sind:

Ein Commit ist durch seinen **SHA-1-Hash** eindeutig identifiziert, und dieser Hash wird berechnet aus:
- dem Inhalt aller Dateien (Tree)
- der Commit-Message
- dem Autor und Zeitstempel
- dem **Parent-Commit** (Vorgänger)

Beim Rebase änderst du den Parent-Commit – deine Commits bekommen einen neuen „Vorgänger". Selbst wenn sich sonst nichts ändert, erzeugt das einen **komplett neuen Hash**. Für Git sind das völlig neue Commits!

```mermaid
flowchart TD
    subgraph vorher["Vor dem Rebase"]
        A1["Commit A\nHash: abc123"] --> B1["Commit B\nHash: def456"]
        B1 --> C1["Dein Commit X\nHash: 111aaa\nParent: def456"]
        C1 --> D1["Dein Commit Y\nHash: 222bbb\nParent: 111aaa"]
    end
    
    subgraph nachher["Nach dem Rebase"]
        A2["Commit A\nHash: abc123"] --> B2["Commit B\nHash: def456"]
        B2 --> E2["Commit C - NEU\nHash: ghi789"]
        E2 --> C2["Dein Commit X - NEU\nHash: 333ccc\nParent: ghi789"]
        C2 --> D2["Dein Commit Y - NEU\nHash: 444ddd\nParent: 333ccc"]
    end
    
    vorher -.->|"git rebase"| nachher
    
    style C1 fill:#ffcccc,color:#000000
    style D1 fill:#ffcccc,color:#000000
    style C2 fill:#ccffcc,color:#000000
    style D2 fill:#ccffcc,color:#000000
```

Die roten Commits (alte Hashes) existieren nach dem Rebase nicht mehr in deinem Branch – stattdessen gibt es die grünen Commits mit **neuen Hashes**. Für Git sind das völlig unterschiedliche Commits, auch wenn sie inhaltlich identisch sein können.

---

## Warum ist das Umschreiben öffentlicher Historie so problematisch?

### 1. **Divergierende Historien im Team** 🔀

Stell dir folgendes Szenario vor:

1. Du hast Commits A → B → C gepusht
2. Dein Kollege hat diese Commits bereits gepullt
3. Du machst einen Rebase und pusht mit `--force`
4. Jetzt hat dein Kollege A → B → C, aber auf dem Server liegt A → B' → C'

Für Git sind das **zwei völlig unterschiedliche Branches**, die zufällig einen gemeinsamen Vorfahren haben. Wenn dein Kollege jetzt pullt oder pusht, passiert Chaos:

```mermaid
flowchart TD
    subgraph server["GitHub - nach deinem Force-Push"]
        S1["A"] --> S2["B'"] --> S3["C'"]
    end
    
    subgraph kollege["Dein Kollege - lokales Repo"]
        K1["A"] --> K2["B"] --> K3["C"] --> K4["D - seine neue Arbeit"]
    end
    
    subgraph problem["Das Problem beim Pull/Push"]
        P1["Git sieht zwei divergierende\nBranches mit gleichem Namen"]
        P2["Merge-Konflikte oder\nduplizierte Commits"]
        P1 --> P2
    end
    
    server -.->|"git pull"| problem
    kollege -.->|"betrifft"| problem
    
    style S2 fill:#ccffcc,color:#000000
    style S3 fill:#ccffcc,color:#000000
    style K2 fill:#ffcccc,color:#000000
    style K3 fill:#ffcccc,color:#000000
    style P2 fill:#ffdddd,color:#000000
```

### 2. **Duplizierte Commits** 👯

Wenn dein Kollege nach deinem Force-Push versucht, seine Arbeit zu synchronisieren, kann es passieren, dass dieselben Änderungen **zweimal in der Historie** auftauchen – einmal mit dem alten Hash, einmal mit dem neuen. Die Historie wird unverständlich und aufgebläht.

### 3. **Verlorene Arbeit** 💀

Noch schlimmer: Wenn jemand auf den „alten" Commits aufgebaut hat und dann deine neue Historie überschreibt, kann dessen Arbeit **komplett verloren gehen** – oder es entstehen massive Merge-Konflikte, die sehr schwer aufzulösen sind.

### 4. **Kaputte Referenzen überall** 🔗

Commits werden an vielen Stellen referenziert:
- **Pull Requests** und Code Reviews verweisen auf spezifische Commit-Hashes
- **Issue-Tracker** und Commit-Messages referenzieren andere Commits
- **CI/CD-Pipelines** haben Build-Ergebnisse für bestimmte Commits gespeichert
- **Deployment-Logs** dokumentieren, welcher Commit deployed wurde
- **Dokumentation** und **Changelogs** können Commit-Hashes enthalten

Nach einem Force-Push zeigen all diese Referenzen ins Leere – die Commits mit diesen Hashes existieren nicht mehr (oder nur noch im Reflog einzelner Entwickler).

### 5. **Vertrauensverlust in die Historie** 📜

Die Git-Historie ist nicht nur ein technisches Artefakt – sie ist auch **Dokumentation**. Teams verlassen sich darauf, dass die Historie korrekt widerspiegelt, was passiert ist. Wenn regelmäßig umgeschrieben wird, verliert die Historie ihren Wert als verlässliche Quelle der Wahrheit.

---

## Was genau bedeutet „öffentlich" oder „geteilt"?

Die Frage ist nicht, ob ein Commit *gepusht* wurde, sondern ob **andere darauf aufbauen könnten**:

| Situation | Rebase erlaubt? | Begründung |
|-----------|-----------------|------------|
| Lokale Commits, nie gepusht | ✅ Ja | Niemand kennt diese Commits |
| Gepusht auf *deinen eigenen* Feature-Branch, noch kein PR | ⚠️ Mit Vorsicht | Wenn du sicher bist, dass niemand anders den Branch gepullt hat |
| Feature-Branch mit offenem PR | ❌ Besser nicht | Reviewer haben möglicherweise den Branch ausgecheckt |
| Feature-Branch, der bereits gemerged wurde | ❌ Nein | Die Commits sind jetzt Teil der Haupthistorie |
| `main`, `develop` oder andere geteilte Branches | ❌ Niemals | Hier baut das ganze Team drauf auf |

---

## Die Ausnahme: Force-Push auf eigene Feature-Branches

Es gibt **eine legitime Situation** für Force-Push, die in vielen Teams akzeptiert oder sogar erwünscht ist:

> **Das Aufräumen deines eigenen Feature-Branches *vor* dem Merge.**

Viele Teams praktizieren folgenden Workflow:

1. Du arbeitest auf einem Feature-Branch und machst viele kleine „WIP"-Commits
2. Du pushst regelmäßig als Backup
3. **Vor dem finalen Review** räumst du auf: Commits zusammenfassen, Messages verbessern
4. Du machst einen Force-Push auf *deinen* Feature-Branch
5. Dann erst wird der PR final reviewed und gemerged

**Voraussetzungen dafür:**
- Der Branch gehört dir allein (niemand anders arbeitet darauf)
- Der PR wurde noch nicht approved/gemerged
- Das Team hat sich auf diesen Workflow geeinigt

In PhpStorm findest du die Force-Push-Option über **Git → Push** und dann den Haken bei **Force Push** (⚠️ nur setzen, wenn du dir sicher bist!).

---

## Was tun, wenn du versehentlich öffentliche Historie umgeschrieben hast? 🚨

Okay, es ist passiert. Du hast einen Force-Push auf einen Branch gemacht, auf dem andere aufbauen. Keine Panik – es gibt Lösungsansätze, aber handle schnell!

### Sofortmaßnahme: Team informieren 📢

**Kommuniziere sofort** mit deinem Team über Slack, Teams, oder wie auch immer ihr kommuniziert:

> „⚠️ Achtung: Ich habe versehentlich einen Force-Push auf [branch-name] gemacht. Bitte noch nicht pullen/pushen, bis wir das geklärt haben!"

Je schneller du kommunizierst, desto weniger Leute sind betroffen.

### Option 1: Alten Zustand wiederherstellen (beste Option, wenn möglich)

Wenn noch niemand (oder fast niemand) die neue Historie gepullt hat, ist die sauberste Lösung, den **alten Zustand wiederherzustellen**:

1. **Finde den alten Commit-Hash** – Der alte Zustand existiert noch im Reflog (lokal) oder vielleicht bei einem Kollegen:
   
   ```bash
   # In deinem lokalen Repository:
   git reflog show origin/main
   ```
   
   Du siehst eine Liste wie:
   ```
   abc1234 origin/main@{0}: update by push --force
   def5678 origin/main@{1}: update by push
   ```
   
   Der Hash `def5678` ist der Zustand *vor* deinem Force-Push.

2. **Setze den Branch zurück** auf den alten Zustand:
   
   ```bash
   git push --force origin def5678:main
   ```
   
   Damit überschreibst du den Branch wieder mit dem ursprünglichen Zustand.

3. **In PhpStorm:**
   
   - Öffne **Git → Show Git Log**
   - Aktiviere **Show All Branches** und suche im Reflog
   - Rechtsklick auf den gewünschten Commit → **Reset Current Branch to Here**
   - Dann **Git → Push** mit Force-Push-Option

### Option 2: Kollegen haben bereits gepullt – Koordiniertes Recovery

Wenn einige Teammitglieder bereits die neue Historie gepullt haben, wird es komplizierter. Hier ist ein koordinierter Ansatz:

1. **Einigt euch auf den „korrekten" Zustand** – Welche Version soll gelten? Die alte oder die neue?

2. **Wenn die alte Version gilt:**
   - Du stellst den alten Zustand wieder her (siehe Option 1)
   - Jeder, der die neue Version hat, muss seinen lokalen Branch zurücksetzen:
     ```bash
     git fetch origin
     git reset --hard origin/main
     ```
   - **Achtung:** Lokale Änderungen, die auf der neuen Historie aufbauen, müssen vorher gesichert werden!

3. **Wenn die neue Version gilt:**
   - Die neue Historie bleibt auf dem Server
   - Jeder muss seinen lokalen Branch an die neue Historie anpassen:
     ```bash
     git fetch origin
     git reset --hard origin/main
     ```
   - Wer eigene Commits auf der alten Historie gemacht hat, muss diese **cherry-picken** oder **rebasen**

### Option 3: Der „Merge-Workaround" für komplizierte Situationen

Wenn das Chaos zu groß ist und ein Reset nicht praktikabel, gibt es einen pragmatischen Workaround:

1. Erstelle einen Branch vom alten Zustand (aus dem Reflog eines Kollegen)
2. Merge diesen Branch in den aktuellen Zustand
3. Damit sind beide Historien zusammengeführt

Das Ergebnis ist keine saubere Historie, aber alle Änderungen sind erhalten und jeder kann normal weiterarbeiten.

### Option 4: Arbeit von betroffenen Kollegen retten

Wenn ein Kollege bereits neue Commits auf der alten Historie gemacht hat, müssen diese gerettet werden:

```mermaid
flowchart TD
    subgraph problem["Ausgangssituation"]
        A1["origin/main - neue Historie\nA → B' → C'"]
        A2["Kollege lokal\nA → B → C → D → E"]
        A3["D und E sind seine neue Arbeit\nauf der alten Historie"]
    end
    
    subgraph loesung["Lösung"]
        B1["1. Kollege notiert sich die Hashes\nvon D und E"]
        B2["2. git fetch origin"]
        B3["3. git reset --hard origin/main"]
        B4["4. git cherry-pick D-hash E-hash"]
        B1 --> B2 --> B3 --> B4
    end
    
    problem --> loesung
    
    style A3 fill:#ffdddd,color:#000000
    style B4 fill:#ccffcc,color:#000000
```

**Der Kollege führt aus:**

```bash
# 1. Hashes der eigenen Commits notieren
git log --oneline  # z.B. abc1234 (E) und def5678 (D)

# 2. Aktuelle Remote-Historie holen
git fetch origin

# 3. Auf die neue Historie wechseln (ACHTUNG: lokale Änderungen gehen verloren!)
git reset --hard origin/main

# 4. Eigene Commits wieder anwenden
git cherry-pick def5678 abc1234  # D und E
```

**In PhpStorm:**

1. **Git → Fetch** ausführen
2. Im Git-Log die eigenen Commits finden und deren Hashes notieren
3. Rechtsklick auf `origin/main` → **Reset Current Branch to Here** (Hard)
4. Im Git-Log die notierten Commits finden → Rechtsklick → **Cherry-Pick**

---

## Präventivmaßnahmen: Wie vermeide ich diesen Fehler in Zukunft? 🛡️

### 1. GitHub Branch Protection aktivieren

Für wichtige Branches wie `main` oder `develop` solltest du **Force-Pushes verbieten**:

1. Gehe zu **Settings → Branches → Add rule**
2. Branch name pattern: `main`
3. Aktiviere: ✅ **Require a pull request before merging**
4. Aktiviere: ✅ **Do not allow force pushes** (unter „Rules applied to everyone")

Damit kann *niemand* (auch keine Admins) einen Force-Push auf diesen Branch machen.

### 2. Git-Konfiguration anpassen

Du kannst Git so konfigurieren, dass Force-Pushes schwieriger werden:

```bash
# Statt --force nutze --force-with-lease
# Dieser Befehl schlägt fehl, wenn jemand anders in der Zwischenzeit gepusht hat
git config --global push.default simple
git config --global alias.pushf "push --force-with-lease"
```

`--force-with-lease` ist eine „sicherere" Version von `--force`: Sie prüft, ob der Remote-Branch noch dem entspricht, was du erwartest. Wenn jemand in der Zwischenzeit gepusht hat, schlägt der Push fehl.

### 3. Lokale Git-Hooks nutzen

Du kannst einen **pre-push Hook** einrichten, der dich warnt:

Erstelle die Datei `.git/hooks/pre-push`:

```bash
#!/bin/bash

# Warnung bei Force-Push auf geschützte Branches
protected_branches="main master develop"

for branch in $protected_branches; do
    if echo "$@" | grep -q "$branch"; then
        echo "⚠️  WARNUNG: Du versuchst auf den geschützten Branch '$branch' zu pushen!"
        echo "   Bist du sicher? (y/n)"
        read -r answer
        if [ "$answer" != "y" ]; then
            echo "Push abgebrochen."
            exit 1
        fi
    fi
done
```

### 4. Workflow-Regel etablieren

Etabliere im Team eine klare Regel:

> **„Rebase nur für lokale Commits oder eigene, noch nicht gemergte Feature-Branches."**

Für alles andere: **Merge verwenden.**

---

## Zusammenfassung: Die wichtigsten Punkte ✨

| Aspekt | Empfehlung |
|--------|------------|
| **Goldene Regel** | Niemals gepushte/geteilte Historie umschreiben |
| **Lokale Commits** | Rebase erlaubt und empfohlen für saubere Historie |
| **Eigene Feature-Branches** | Mit Vorsicht, wenn niemand anders darauf arbeitet |
| **Geteilte Branches** | Niemals rebasen – immer mergen |
| **Wenn es passiert ist** | Schnell kommunizieren, alten Zustand aus Reflog wiederherstellen |
| **Prävention** | Branch Protection, `--force-with-lease`, Team-Kommunikation |

Die goldene Regel schützt nicht nur dein Team vor Chaos, sondern auch dich selbst: Nichts ist frustrierender, als Stunden damit zu verbringen, ein Repository-Chaos zu entwirren, das durch einen unbedachten Force-Push entstanden ist. **Im Zweifel: Merge statt Rebase!** 🎯