This commit is contained in:
Mathias Schneider
2026-09-07 08:39:53 +02:00
parent e19978fe6d
commit f184fcdb2e
@@ -0,0 +1,473 @@
# Spickzettel: Gestapelten Feature-Branch nach einem Squash-Merge aktualisieren
Ausgangssituation:
```text
main: A----------------S
\ /
feature1: B------------C
\
feature2: D---E
```
- `feature1` wurde von `main` abgezweigt.
- `feature2` wurde von `feature1` abgezweigt.
- `feature1` wurde anschließend per **Squash-Merge** in `main` integriert.
- `feature2` enthält sowohl die Änderungen von `feature1` als auch eigene Änderungen.
- Jetzt soll `feature2` auf dem aktuellen `main` basieren und nur noch seine eigenen Änderungen zusätzlich enthalten.
Ziel:
```text
main: A----------------S
\
feature2: D'---E'
```
Die Commits `D` und `E` werden dabei als neue Commits `D'` und `E'` auf die neue Basis gesetzt.
---
## 1. Arbeitsstand prüfen
Zuerst sicherstellen, dass keine ungesicherten Änderungen vorhanden sind:
```bash
git status
```
Idealerweise lautet die Ausgabe sinngemäß:
```text
nothing to commit, working tree clean
```
Falls noch Änderungen vorhanden sind, entweder committen oder vorübergehend weglegen:
```bash
git stash push -m "temporäre Änderungen vor Rebase"
```
---
## 2. Remote-Branches aktualisieren
```bash
git fetch origin
```
Damit werden beispielsweise diese Referenzen aktualisiert:
```text
origin/main
origin/feature1
origin/feature2
```
Wichtig: `origin/main` ist der aktuelle Stand des Remote-Branches. Er enthält jetzt den Squash-Commit von `feature1`.
---
## 3. Prüfen, auf welchem Branch man arbeitet
```bash
git switch feature2
```
Anschließend:
```bash
git status
```
Es sollte erkennbar sein, dass man sich auf `feature2` befindet.
Optional kann man sich die Historie grafisch ansehen:
```bash
git log --oneline --graph --decorate --all
```
---
## 4. Den alten Basis-Commit identifizieren
Für den Rebase muss Git wissen:
> Ab welchem Commit beginnen die Änderungen, die ausschließlich zu `feature2` gehören?
Wenn der Branch `feature1` noch existiert und unverändert auf seinem ursprünglichen Stand steht, ist der Standardfall:
```bash
git rebase --onto origin/main origin/feature1 feature2
```
Das bedeutet:
```text
git rebase --onto <neue-Basis> <alte-Basis> <Branch>
```
Konkret:
- `origin/main`: neue Basis
- `origin/feature1`: alte Basis, die nicht erneut übernommen werden soll
- `feature2`: Branch, dessen eigene Commits neu aufgesetzt werden
Git nimmt also alle Commits, die in `feature2`, aber nicht in `feature1` enthalten sind, und wendet sie auf `origin/main` an.
Das ist in der Regel genau das gewünschte Verhalten.
---
## 5. Vorher ein Sicherheitsbackup anlegen
Ein Rebase schreibt die Branch-Historie um. Deshalb ist ein Backup empfehlenswert:
```bash
git switch feature2
git branch backup/feature2-vor-rebase
```
Falls etwas schiefgeht, kann man jederzeit auf diesen Stand zurückgreifen:
```bash
git switch backup/feature2-vor-rebase
```
Oder den ursprünglichen Stand wiederherstellen:
```bash
git switch feature2
git reset --hard backup/feature2-vor-rebase
```
---
## 6. Rebase durchführen
Mit weiterhin ausgechecktem `feature2`:
```bash
git rebase --onto origin/main origin/feature1
```
Die Kurzform funktioniert hier, weil Git den aktuell ausgecheckten Branch verwendet.
Alternativ explizit:
```bash
git rebase --onto origin/main origin/feature1 feature2
```
Nach dem erfolgreichen Rebase sieht die Historie ungefähr so aus:
```text
main: A----------------S
\
feature2: D'---E'
```
Die Änderungen aus `feature1` werden nicht noch einmal als eigene Commits übernommen. Sie sind bereits im Squash-Commit auf `main` enthalten.
---
# Falls Konflikte auftreten
Konflikte sind bei diesem Vorgang möglich, insbesondere wenn:
- der Squash-Merge in GitLab manuell angepasst wurde,
- `feature2` dieselben Dateien wie `feature1` weiter verändert,
- sich `main` seit dem Merge weiterentwickelt hat.
Git zeigt die betroffenen Dateien an:
```bash
git status
```
Die Konfliktmarkierungen sehen beispielsweise so aus:
```text
<<<<<<< HEAD
Stand von main
=======
Stand aus feature2
>>>>>>> <commit>
```
Die Datei muss manuell in den gewünschten Endzustand gebracht werden. Danach:
```bash
git add pfad/zur/datei
```
Wenn alle Konflikte des aktuellen Rebase-Schritts gelöst sind:
```bash
git rebase --continue
```
Git kann anschließend weitere Konflikte melden. Dann wiederholt man:
```bash
git status
# Dateien bearbeiten
git add <dateien>
git rebase --continue
```
## Einen Rebase abbrechen
Wenn der Rebase nicht sinnvoll fortgesetzt werden kann:
```bash
git rebase --abort
```
Damit wird `feature2` auf den Zustand vor Beginn des Rebases zurückgesetzt.
---
# 7. Ergebnis überprüfen
Nach dem Rebase zunächst die Historie ansehen:
```bash
git log --oneline --graph --decorate -20
```
Außerdem prüfen, welche Änderungen `feature2` gegenüber `main` enthält:
```bash
git diff origin/main...feature2
```
Das sollte im Wesentlichen nur die Änderungen des zweiten Features zeigen.
Auch die Commit-Liste kann geprüft werden:
```bash
git log --oneline origin/main..feature2
```
Dort sollten nur die eigenen Commits von `feature2` auftauchen, nicht mehr die ursprünglichen Commits von `feature1`.
Zusätzlich:
```bash
git status
```
---
# 8. Den umgeschriebenen Branch pushen
Da ein Rebase die Commit-IDs ändert, ist ein normaler Push meist nicht möglich. Verwenden Sie:
```bash
git push --force-with-lease origin feature2
```
Nicht bevorzugt:
```bash
git push --force origin feature2
```
`--force-with-lease` ist sicherer, weil Git den Push ablehnt, wenn zwischenzeitlich jemand anderes neue Änderungen auf `origin/feature2` veröffentlicht hat.
Danach sollte der Merge Request von `feature2` nach `main` aktualisiert sein. Im Diff sollten nun normalerweise nur noch die Änderungen des zweiten Features erscheinen.
---
# Der wichtigste Standardbefehl
Wenn `feature1` noch existiert und auf dem alten Stand steht:
```bash
git fetch origin
git switch feature2
git branch backup/feature2-vor-rebase
git rebase --onto origin/main origin/feature1
git push --force-with-lease origin feature2
```
Dabei sollte man den Rebase-Schritt nicht blind überspringen, sondern Konflikte und den anschließenden Diff prüfen.
---
# Wenn `feature1` bereits gelöscht wurde
Wenn der Remote-Branch `feature1` nach dem Merge gelöscht wurde, funktioniert dieser Befehl nicht mehr:
```bash
git rebase --onto origin/main origin/feature1
```
Dann braucht man den alten Stand von `feature1`, also den Commit, auf dem `feature2` ursprünglich basierte.
## Möglichkeit A: Lokale Referenz vorhanden
Prüfen:
```bash
git branch -a
```
Vielleicht existiert noch ein lokaler Branch:
```text
feature1
```
Dann kann man verwenden:
```bash
git rebase --onto origin/main feature1 feature2
```
## Möglichkeit B: Commit aus der Historie verwenden
Mit der Historie:
```bash
git log --oneline --graph --all
```
den letzten Commit von `feature1` vor den eigenen `feature2`-Commits suchen.
Angenommen, dieser Commit heißt:
```text
abc1234
```
Dann:
```bash
git rebase --onto origin/main abc1234 feature2
```
Bedeutung:
> Alle Commits nach `abc1234`, die zu `feature2` gehören, auf `origin/main` setzen.
## Möglichkeit C: Reflog verwenden
Wenn der Commit nicht mehr über die normale Historie sichtbar ist:
```bash
git reflog
```
Dort kann man nach dem ursprünglichen Abzweigpunkt oder dem früheren Stand von `feature1` suchen.
---
# Variante ohne `rebase --onto`: eigene Commits per Cherry-Pick übernehmen
Wenn die Branch-Historie unklar ist, kann man auch einen neuen Branch auf Basis von `main` erstellen und nur die eigenen `feature2`-Commits übernehmen.
Beispiel:
```bash
git fetch origin
git switch -c feature2-neu origin/main
git cherry-pick <commit-D>^..<commit-E>
```
Dabei werden die eigenen Commits von `feature2` einzeln übernommen.
Danach kann der neue Branch entweder als neuer Merge Request verwendet oder nach sorgfältiger Prüfung an die Stelle von `feature2` gepusht werden.
Diese Variante ist nützlich, wenn:
- `feature1` gelöscht wurde,
- die ursprüngliche Branch-Basis nicht mehr eindeutig ist,
- `feature2` nur wenige eigene Commits enthält.
---
# Was man nicht tun sollte
Nach einem Squash-Merge von `feature1` ist dieser Ablauf problematisch:
```bash
git switch feature2
git merge origin/main
```
Das kann funktionieren, muss aber nicht. Git sieht die ursprünglichen Commits von `feature1` weiterhin in der Historie von `feature2`, während `main` einen neuen Squash-Commit enthält. Dadurch können Änderungen doppelt betrachtet oder Konflikte erzeugt werden.
Für diesen speziellen Fall ist besser:
```bash
git rebase --onto origin/main origin/feature1
```
---
# Alternative: `feature1` nicht squashen
Wenn häufig gestapelte Branches verwendet werden, kann es sinnvoll sein, den übergeordneten Branch `feature1` nicht zu squashen, sondern per normalem Merge zu integrieren.
Dann bleibt die Commit-Abstammung erhalten:
```text
main: A---------M
\ /
feature1: B-----C
\
feature2: D---E
```
Git kann in diesem Fall besser erkennen, dass `B` und `C` bereits in `main` enthalten sind.
Eine mögliche Teamregel wäre daher:
- unabhängige Feature-Branches: Squash-Merge erlaubt
- voneinander abhängige oder gestapelte Feature-Branches: normaler Merge ohne Squash oder anschließend gezielter Rebase
---
# Zusammenfassung
Für Ihren Fall ist der typische Ablauf:
```bash
git status
git fetch origin
git switch feature2
git branch backup/feature2-vor-rebase
git rebase --onto origin/main origin/feature1
```
Bei Konflikten:
```bash
# Dateien bearbeiten
git add <dateien>
git rebase --continue
```
Bei Abbruch:
```bash
git rebase --abort
```
Nach erfolgreichem Rebase prüfen:
```bash
git diff origin/main...feature2
git log --oneline origin/main..feature2
```
Dann pushen:
```bash
git push --force-with-lease origin feature2
```
Der entscheidende Punkt ist: **Nach einem Squash-Merge muss ein davon abgeleiteter Branch auf die neue `main`-Basis neu aufgebaut werden.** Ein einfacher Merge von `main` in den alten gestapelten Branch ist dafür nicht die zuverlässigsten Variante.