diff --git a/git/Gestapelten Feature-Branch nach einem Squash-Merge aktualisieren.md b/git/Gestapelten Feature-Branch nach einem Squash-Merge aktualisieren.md new file mode 100644 index 0000000..52572d9 --- /dev/null +++ b/git/Gestapelten Feature-Branch nach einem Squash-Merge aktualisieren.md @@ -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 +``` + +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 +>>>>>>> +``` + +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 +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 ^.. +``` + +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 +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. \ No newline at end of file