# 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.